---
description: Exxat DS — show Kbd hints on primary/secondary actions, search, Ask Leo; avoid browser-reserved chords.
alwaysApply: false
globs: 
  - components/**/*.tsx
tags: [keyboard, a11y]
seeAlso:
  - ./AGENTS.md
---

# Exxat DS — keyboard shortcuts (`Kbd`)

## When to show `Kbd`

Use `@/components/ui/kbd` (`Kbd` + `KbdGroup`) anywhere users discover actions by hovering or reading tooltips:

- **Primary actions** — main page CTAs (e.g. “New …”, “Save”, “Submit”).
- **Secondary actions** — overflow menus, “More”, outline companions to a primary button.
- **Global affordances** — **Search** (table toolbar), **Ask Leo**, **Toggle sidebar**.

## Rules

0. **`Kbd` variant MUST match its host surface** (this is the #1 recurring mistake):

   | Where the `Kbd` renders | Required variant |
   |-------------------------|------------------|
   | Inside a `Button` (primary, secondary, wizard Next/Back/Submit, full-width CTAs in popovers) | **`variant="bare"`** — no bg/border, inherits `currentColor` @ 70 % |
   | Inside a `TooltipContent` | **default `tile`** (no prop) |
   | Inside a `DropdownMenuItem` via `shortcut=` | menu handles it — pass the chord string |
   | Standalone helper text on a surface | **default `tile`** |

   Glue multi-key chords into **one** bare kbd (`<Kbd variant="bare">⌘⌥K</Kbd>`), not one tile per key. See `new-library-item-form.tsx` (Next = `{mod}⏎`, Back = `{mod}{alt}←`) and the primary "Ask Leo" button inside chart insight popovers.

1. **Pair hint with behavior** — If `Kbd` shows a chord, implement the same shortcut. **Preferred:** the shared primitives from `@/components/ui/dropdown-menu`:

    ```tsx
    import { DropdownMenuItem, Shortcut } from "@/components/ui/dropdown-menu"

    // Visual hint in a menu item:
    <DropdownMenuItem shortcut="⌘⇧E" onSelect={onExport}>Export</DropdownMenuItem>

    // Global binding — render in a parent that stays mounted (menu items unmount on close):
    <Shortcut keys="⌘⇧E" onInvoke={onExport} />
    ```

    The hook skips input/textarea/contenteditable targets and any open dialog. Accepts symbols (`⌘⇧⌥⌃⌫⌦⏎↑↓`) and words (`Cmd+Shift+D`, `Alt+P`). Avoid ad-hoc `document.addEventListener("keydown", …)` + `isEditableTarget` — use `<Shortcut>` instead.
2. **Modifier labels** — Use `useModKeyLabel()` (**⌘** / **Ctrl**) and `useAltKeyLabel()` (**⌥** / **Alt**) from `@/hooks/use-mod-key-label` for tooltips.
3. **Avoid browser-reserved chords** — Do **not** use combinations that match common browser defaults, including:
   - **⌘⇧N / Ctrl+Shift+N** — private/incognito window
   - **⌘⇧T / Ctrl+Shift+T** — reopen closed tab
   - **⌘⇧O / Ctrl+Shift+O** — bookmark manager (Chromium)
   - **⌘⇧B / Ctrl+Shift+B** — bookmarks bar
   - **⌘L / Ctrl+L** — focus address bar (avoid plain **L** with only mod unless scoped)
   - **⌃⌥L / Ctrl+Alt+L** — screen lock on many Linux desktops (avoid for in-app actions)
4. **Preferred pattern for app shortcuts** — Use **⌘⌥** / **Ctrl+Alt** + letter (e.g. **⌘⌥N** for “New”, **⌘⌥M** for “More”, **⌘⌥K** for Ask Leo). Table search stays **⌘K** / **Ctrl+K** **without** Alt so **⌘⌥K** does not collide.
5. **Tooltips** — `Tip` supports `label` as `React.ReactNode` so you can compose text + `KbdGroup`.
6. **Do not** decorate every control — skip dense tables, icon-only row actions that already have `aria-label`, and third-party widgets.

## Reference shortcuts (app)

| Action | Shortcut |
|--------|----------|
| Toggle main sidebar | ⌘/Ctrl + **B** (`components/ui/sidebar.tsx`) |
| Table search | ⌘/Ctrl + **K** (no Alt — `DataTable`) |
| Ask Leo | ⌘/Ctrl + **⌥/Alt** + **K** |
| New record (primary hub header) | ⌘/Ctrl + **⌥/Alt** + **N** |
| Hub overflow menu (⋯) | ⌘/Ctrl + **⌥/Alt** + **M** |
| Export | ⌘/Ctrl + **⇧/Shift** + **E** |
| Hide/Show metric section | ⌘/Ctrl + **⌥/Alt** + **H** |
| Rename (view, tab) | **F2** |
| Duplicate | ⌘/Ctrl + **D** |
| Review / Info | ⌘/Ctrl + **I** |
| Remove / Delete | ⌘/Ctrl + **⌫** |
| Add view (1..n) | **1..9** (plain digit; skipped in inputs / open dialogs) |
| **Submit a workflow** (Create, Save, Export, Apply) | **Enter** (⏎) — scoped to the form/drawer/dialog |
| **Cancel / dismiss** a workflow | **Esc** (Radix handles for Dialog/Sheet) |
| **Advance a multi-step wizard** | ⌘/Ctrl + **Enter** (plain Enter stays in the input) |
| **Back** in a wizard | ⌘/Ctrl + **⌥/Alt** + **←** |

## Every workflow primary/secondary action MUST carry shortcuts

Every **workflow surface** (form, dialog, drawer, sheet, multi-step wizard final step) MUST bind:

1. **Primary action (submit/commit)** — **Enter** (⏎). Render the `<Kbd>⏎</Kbd>` **inline inside the button** (after the label, inside a `<KbdGroup className="ml-1.5">`) — NOT inside a hover `Tip`. Primary/secondary workflow buttons must expose the shortcut at rest so it is discoverable without hovering. Pair with a `<Shortcut keys="Enter" onInvoke={...}>` mounted while the surface is open. The shared `useShortcut` hook skips events from inputs/textarea/contenteditable, so Enter inside a text field still types normally — it only fires when focus is on the surface chrome.
2. **Secondary action (Cancel/Dismiss)** — **Esc**. Inline `<Kbd>Esc</Kbd>` inside the Cancel button (same `ml-1.5` pattern). Radix `Dialog` / `Sheet` / `AlertDialog` already bind Esc natively.

> Tip-on-hover Kbd hints remain correct for **page-level** actions (e.g. primary "New …" CTA, ⋯ overflow triggers) where the button is part of dense page chrome and a persistent Kbd would crowd the layout. Workflow buttons inside a form/drawer/dialog are spacious enough to render the Kbd inline.

**Variant inside a button:** always use `<Kbd variant="bare">` — no background, no border, inherits `currentColor` at 70% opacity. The default tile variant looks like a pasted-on patch on filled primary buttons. Glue multi-key chords into one `<Kbd variant="bare">⌘⌥←</Kbd>` rather than one tile per key.
3. **Multi-step wizards** — plain **Enter** must NOT submit on intermediate steps (it would auto-close the review/final step when users hit Enter inside an input). Either:
   - Gate `form.onSubmit` on `step === lastStep` (`if (step !== N) { e.preventDefault(); return }`), **or**
   - Remove `type="submit"` on intermediate Next buttons and bind **⌘Enter** to "Next" via `<Shortcut>`.
   On the final step, plain **Enter** submits and the Kbd hint shows **⏎**.
4. Examples in-app: `new-library-item-form.tsx` (Create = Enter on the final step, Back = ⌘⌥←), `export-drawer.tsx` (Export = Enter, Cancel = Esc).

## Every action menu MUST carry shortcuts

All dropdown action menus (⋯ overflow, view-settings chevron, Add view, row actions) should declare a `shortcut=` on each `DropdownMenuItem` AND pair it with a `<Shortcut>` in a parent that stays mounted. This covers both discoverability (visual hint) and operability (global key binding).

Adjust this table when adding new global shortcuts.

## See also

- **`./AGENTS.md`** §7 — keyboard rules in project context.
- **`./AGENTS.md`** §7.1 — global command palette (⌘K) vs **Ask Leo** (**⌘⌥K**); **`docs/exxat-ds/command-menu-pattern.md`**.
