Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Shortcut Overlay

The ?-triggered dialog listing keyboard shortcuts, rendered from a declaration registry — declare once with a category, never hand-list.

Status
stable
Since
0.4.0
Accessibility pattern
description list in a dialog

Last updated

Or press ? anywhere outside a text field.

Install

Add Shortcut Overlay from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/shortcut-overlay

The same command installs the registry items it composes: @vegastack/dialog, @vegastack/floating-surface, @vegastack/kbd, @vegastack/scroll-area, @vegastack/use-platform.

Usage

import {
  ShortcutOverlay,
  type ShortcutDefinition,
} from "@/components/ui/shortcut-overlay";

const SHORTCUTS: ShortcutDefinition[] = [
  { keys: ["⌘", "K"], label: "Open command menu", category: "Navigation" },
  { keys: ["E"], label: "Edit selected record", category: "Editing" },
];

<ShortcutOverlay shortcuts={SHORTCUTS} />;

The value is the registry model, not the dialog: a hand-listed shortcuts dialog goes stale the day someone adds a binding. Declare each shortcut once — keys, label, category, an optional when — and the overlay renders from data. The same declarations can feed tooltip hints and Command rows.

Keys are mac-first; Kbd's modifier rewriting is driven by use-platform, so the command glyph renders as Ctrl for non-mac users automatically (and mac glyphs carry spoken-name text for screen readers).

Scope

BehaviourWhere it lives
Executing the shortcutsThe app's key handler or command layer
Rebinding / customisationApp policy — deliberately no persistence here
Inline hintsTooltipKbd / CommandShortcut, fed by the same data

Examples

Large sets get a filter

The filter appears automatically past ten shortcuts (or force it with searchable).

API Reference

PropTypeDefaultDescription
shortcuts*readonly ShortcutDefinition[]The declared shortcuts. Categories render in first-appearance order.
onOpenChange((open: boolean) => void)Fired when the overlay wants to open or close.
openbooleanControlled open state. Pair with onOpenChange; omit to let the built-in <kbd>?</kbd> binding manage it.
searchablebooleanShow the filter input. On by default for more than ten shortcuts.
shouldHandle(() => boolean)Predicate consulted before the trigger key opens the overlay — return false while another overlay owns the keyboard (the same suppression rule useListNav uses).
titlestring"Keyboard shortcuts"Dialog title.
triggerKeystring | false"?"Bind the global open key. false disables the built-in binding (the host opens the overlay itself). The binding never fires while focus is in a text field or another editable surface.

Data attributes and CSS variables on ShortcutOverlay

AttributeValues
data-slot"shortcut-overlay" | "shortcut-overlay-category" | "shortcut-overlay-empty" | "shortcut-overlay-row"

Shortcut definition

PropTypeDefaultDescription
category*stringSection the shortcut is grouped under ("Navigation", "Editing").
keys*readonly string[]Keys as rendered, mac-first (["⌘", "K"]). Kbd's os="other" rewriting translates the modifier glyphs for non-mac platforms automatically.
label*stringWhat the shortcut does — verb + noun ("Open command menu").
whenbooleanExclude this shortcut when false — for bindings gated by permission or context. Omitted (undefined) includes it.

Accessibility

  • Shortcuts render as a description list, so each label/keys pair is announced as a pair; the keys are real <kbd> elements.
  • The ? binding never fires while focus is in an input, textarea, select, or contenteditable surface, and defers to shouldHandle while another overlay owns the keyboard.
  • The dialog is labelled and closes on Escape; category headers are real headings.
KeyAction
?Open the overlay (outside text fields).
EscapeClose it.
ContractStates tested
Behaviourclosed, open, filtered, empty-filter, gated-when, suppressed
Accessibilitykeyboard, labeled, semantic-html
Visualdefault, empty, dark

Do / Don't

Do
Keep one shortcut registry and feed the overlay, tooltips, and Command rows from it.
Don't
Hand-list shortcuts in dialog markup — it goes stale the day someone adds a binding.

On this page