Menu

Change language

GitHub repo

Shortcuts Dialog

blocklayout

Linear-style keyboard reference sheet: a '?' dialog listing shortcut groups (label, description, Kbd combo) in a responsive two-column grid. Ships a built-in trigger button, controlled/uncontrolled open, custom groups via prop, and an Esc footer hint. Display only — wire real handlers in your app.

Installation

$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/shortcuts-dialog.json
Manifest:https://uipkge.dev/r/svelte/shortcuts-dialog.jsonInstalls to:src/lib/components/blocks/shortcuts-dialog/

Variants

Loading interactive previews…

npm dependencies

Files installed (1)

  • src/lib/components/blocks/shortcuts-dialog/ShortcutsDialog.svelte4.5 kB
    <script lang="ts" module>
      import type { Snippet } from 'svelte'
    
      export interface Shortcut {
        keys: string[]
        description: string
      }
    
      export interface ShortcutGroup {
        label: string
        shortcuts: Shortcut[]
      }
    
      export interface ShortcutsDialogProps {
        groups?: ShortcutGroup[]
        /** Render the built-in trigger button. */
        trigger?: boolean
        /**
         * Custom trigger content. Receives the trigger `props` (open handler,
         * a11y attributes) — spread them onto your element. Used only when
         * `trigger` is false.
         */
        triggerSnippet?: Snippet<[{ props: Record<string, unknown> }]>
        /** Show the 'Press Esc to close' footer hint. */
        footer?: boolean
        /** Controlled open state. Omit for uncontrolled (internal state). */
        open?: boolean
        onOpenChange?: (open: boolean) => void
        class?: string
      }
    </script>
    
    <script lang="ts">
      import { Keyboard } from '@lucide/svelte'
      import { cn } from '$lib/utils'
      import { Button } from '$lib/components/ui/button'
      import {
        Dialog,
        DialogContent,
        DialogDescription,
        DialogFooter,
        DialogHeader,
        DialogTitle,
        DialogTrigger,
      } from '$lib/components/ui/dialog'
      import { Kbd } from '$lib/components/ui/kbd'
    
      let {
        groups = [
          {
            label: 'Navigation',
            shortcuts: [
              { keys: ['G', 'I'], description: 'Go to inbox' },
              { keys: ['G', 'D'], description: 'Go to drafts' },
              { keys: ['⌘', 'K'], description: 'Open command menu' },
              { keys: ['⌥', '↑', '↓'], description: 'Move between items' },
            ],
          },
          {
            label: 'Actions',
            shortcuts: [
              { keys: ['C'], description: 'Compose new' },
              { keys: ['/'], description: 'Focus search' },
              { keys: ['⌘', 'S'], description: 'Save changes' },
              { keys: ['E'], description: 'Archive selection' },
            ],
          },
          {
            label: 'General',
            shortcuts: [
              { keys: ['?'], description: 'Show this dialog' },
              { keys: ['⇧', '?'], description: 'Show cheat sheet' },
              { keys: ['Esc'], description: 'Close dialogs' },
            ],
          },
        ],
        trigger = true,
        triggerSnippet,
        footer = true,
        open = $bindable(undefined),
        onOpenChange,
        class: className,
      }: ShortcutsDialogProps = $props()
    
      // Local state drives the uncontrolled case; the `open` prop takes precedence.
      let internalOpen = $state(false)
      const isOpen = $derived(open ?? internalOpen)
    
      function handleOpenChange(value: boolean) {
        internalOpen = value
        open = value
        onOpenChange?.(value)
      }
    </script>
    
    <!-- Dialog renders only its children, so the slot marker lives on a layout-free wrapper. -->
    <div data-slot="shortcuts-dialog" class="contents">
    <Dialog open={isOpen} onOpenChange={handleOpenChange}>
      {#if trigger}
        <DialogTrigger>
          {#snippet child({ props })}
            <Button variant="outline" size="sm" {...props}>
              <Keyboard aria-hidden="true" />
              Shortcuts
              <Kbd>?</Kbd>
            </Button>
          {/snippet}
        </DialogTrigger>
      {:else if triggerSnippet}
        <DialogTrigger>
          {#snippet child({ props })}
            {@render triggerSnippet({ props })}
          {/snippet}
        </DialogTrigger>
      {/if}
      <DialogContent class={cn('max-w-lg', className)}>
        <DialogHeader>
          <DialogTitle>Keyboard shortcuts</DialogTitle>
          <DialogDescription>Move faster without leaving the keyboard.</DialogDescription>
        </DialogHeader>
        <div class="grid max-h-[60vh] gap-x-8 gap-y-6 overflow-y-auto pr-1 sm:grid-cols-2">
          {#each groups as group (group.label)}
            <div class="space-y-2">
              <p class="text-muted-foreground text-xs font-medium tracking-widest uppercase">{group.label}</p>
              <ul class="divide-y">
                {#each group.shortcuts as shortcut (shortcut.description)}
                  <li class="flex items-center justify-between gap-4 py-1.5">
                    <span class="text-sm">{shortcut.description}</span>
                    <span class="flex shrink-0 items-center gap-1">
                      {#each shortcut.keys as key (key)}
                        <Kbd>{key}</Kbd>
                      {/each}
                    </span>
                  </li>
                {/each}
              </ul>
            </div>
          {/each}
        </div>
        {#if footer}
          <DialogFooter class="justify-start">
            <p class="text-muted-foreground flex items-center gap-1.5 text-xs">Press <Kbd>Esc</Kbd> to close</p>
          </DialogFooter>
        {/if}
      </DialogContent>
    </Dialog>
    </div>
    

Raw manifest:https://uipkge.dev/r/svelte/shortcuts-dialog.json