Shortcut
Keyboard shortcut badges for documenting and displaying key combinations. Canonical key names resolve to the platform's own modifiers and to Apple's symbols, each keycap carrying the name a screen reader announces.
Playground
Installation
pnpm add @tessinaui/uiUsage
import { Key, Shortcut } from "@tessinaui/ui";{/* Single key */}
<Key>Esc</Key>
{/* Shorthand — keys array */}
<Shortcut keys={["Ctrl", "K"]} />
<Shortcut keys={["Cmd", "Shift", "P"]} />
{/* "mod" — ⌘ on Apple keyboards, Ctrl everywhere else */}
<Shortcut keys={["mod", "K"]} />
{/* Apple symbols, on Apple platforms */}
<Shortcut keys={["mod", "shift", "p"]} glyphs />
{/* Force a platform — previews, tests, server-resolved rendering */}
<Shortcut keys={["mod", "K"]} glyphs platform="mac" />
{/* On a colored surface */}
<Surface background="primary">
<Shortcut keys={["mod", "K"]} tone="on-color" />
</Surface>
{/* Compound — children for custom compositions */}
<Shortcut>
<Key>Ctrl</Key>
<Key>K</Key>
</Shortcut>
{/* Mixed rounded per-key */}
<Shortcut>
<Key>Cmd</Key>
<Key rounded="full">⌘</Key>
</Shortcut>
{/* Rounded variant */}
<Shortcut keys={["Ctrl", "S"]} rounded="full" />
{/* Custom separator */}
<Shortcut keys={["Ctrl", "K"]} separator="then" />
{/* Size */}
<Shortcut keys={["Ctrl", "S"]} size="lg" />
{/* RTL */}
<Shortcut keys={["Ctrl", "Z"]} dir="rtl" />Examples
Default
A keyboard combination rendered from the keys array with auto-inserted separators.
Single Key
The standalone Key component for individual keys like Esc or Enter.
Sizes
Five sizes — xs, sm, md, lg, and xl.
Rounded
Border radius variants — none, sm, md, lg, and full.
Separators
Customize the character shown between keys with the separator prop.
Compound
Compose individual Key children for per-key control, like mixing rounded styles.
Glyphs
glyphs swaps the words for Apple's symbols on Apple platforms. Windows and
Linux keep the words — they have no glyph convention to follow.
Platform
"mod" is the platform's primary modifier: ⌘ on an Apple keyboard, Ctrl
everywhere else. platform forces the answer when you need a deterministic one.
On color
tone="on-color" puts keycaps on a colored surface. Inside a <Surface> it is
inherited, so nested shortcuts adapt on their own.
RTL
dir="rtl" mirrors the key order for right-to-left layouts.
In a button
A hint inside the control it belongs to. It sits in trailingIcon, which the
button renders aria-hidden, and the shortcut is announced by
aria-keyshortcuts on the button instead — otherwise a screen reader reads
"Accept Enter" as though the key were part of the label.
In a tooltip
The familiar pattern: hover a control, get one line naming it plus the keys that trigger it.
It composes HoverCard rather than Tooltip, because Tooltip is a static card
— it has content slots but no trigger, so it cannot appear on hover. Three
overrides turn a card into a tooltip: w-auto max-w-none so the popup
shrink-wraps its line, whitespace-nowrap to keep it to one line, and shorter
open/close delays.
If you want the static card instead, Tooltip takes a shortcuts array directly
and scales the caps against the card — an lg card gets md caps, an sm card
gets xs — so nothing needs composing by hand there.
In a search field
Search accepts showShortcut, which puts a ⌘K cap in the field, swaps it for
Esc once there is a query, and binds the global focus listener to match.
API Reference
Key props
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Visual size of the key badge (inherits from parent Shortcut) |
rounded | "none" | "sm" | "md" | "lg" | "full" | "md" | Border radius (inherits from parent Shortcut) |
variant | "secondary" | "outline" | "ghost" | "secondary" | Visual weight (§1). secondary is the filled plate, outline trades the fill for an edge, ghost is the bare glyph with no plate at all. Every weight keeps the same box — the border is present and transparent in all three — so switching never shifts the mark |
tone | "default" | "on-color" | inherited | On-color treatment for colored surfaces; inherited from <Surface> or a parent Shortcut |
glyphs | boolean | false | Render Apple's modifier symbols on Apple platforms (inherits from parent Shortcut) |
platform | "mac" | "other" | auto-detected | Force a keyboard convention instead of detecting one |
className | string | — | Additional CSS classes |
All native <kbd> HTML attributes are forwarded.
The exported keyVariants gives access to the same class map for one-off
compositions.
Shortcut props
| Prop | Type | Default | Description |
|---|---|---|---|
keys | string[] | — | Shorthand: array of key names with auto-inserted separators |
children | ReactNode | — | Compound: Key components as children — separators auto-injected between them |
separator | string | "+" | Character(s) displayed between keys. Defaults to "" under glyphs on Apple platforms |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Propagated to all child Key components via context |
rounded | "none" | "sm" | "md" | "lg" | "full" | "md" | Propagated to all child Key components via context |
variant | "secondary" | "outline" | "ghost" | "secondary" | Propagated to all child Key components via context |
tone | "default" | "on-color" | inherited | On-color treatment; inherited from a wrapping <Surface> |
glyphs | boolean | false | Render Apple's modifier symbols on Apple platforms |
platform | "mac" | "other" | auto-detected | Force a keyboard convention instead of detecting one |
dir | "ltr" | "rtl" | "ltr" | Text direction for RTL layouts |
className | string | — | Additional CSS classes |
children takes precedence over keys when both are provided. Native <span>
attributes are forwarded; dir is re-typed to the two values above.
Key names
Names are matched case-insensitively, and common aliases are accepted. Anything unrecognised renders exactly as written, so app-specific keys still work.
| Name | Aliases | Apple | Windows / Linux | Announced as |
|---|---|---|---|---|
mod | — | ⌘ / Cmd | Ctrl | Command / Control |
command | cmd, meta | ⌘ / Cmd | Meta | Command / Meta |
control | ctrl | ⌃ / Ctrl | Ctrl | Control |
shift | — | ⇧ / Shift | Shift | Shift |
alt | option, opt | ⌥ / Alt | Alt | Option / Alt |
enter | return | ↵ / Enter | Enter | Return / Enter |
escape | esc | Esc | Esc | Escape |
tab | — | ⇥ / Tab | Tab | Tab |
backspace | — | ⌫ / Backspace | Backspace | Backspace |
delete | del | ⌦ / Delete | Delete | Delete |
space | spacebar | Space | Space | Space |
up down left right | arrowup, … | ↑ ↓ ← → | ↑ ↓ ← → | Up Arrow, … |
pageup pagedown | pgup, pgdn | ⇞ ⇟ / PgUp | PgUp PgDn | Page Up / Page Down |
home end | — | Home End | Home End | Home / End |
capslock | caps | ⇪ / Caps | Caps | Caps Lock |
fn | — | Fn | Fn | Function |
f1–f19 | — | F1 | F1 | F1 |
The symbol column applies under glyphs; otherwise the word is used. escape
and space have no symbol on purpose — ⎋ reads to almost nobody.
Modifiers render in the order you write them. The convention worth following is Apple's: ⌃ ⌥ ⇧ ⌘, modifiers before the key.
One key, one cap
Every key gets its own keycap, and single-key caps are uniform squares — a ⌘
cap, a C cap and a ⌫ cap are always the same size. Never put a whole run in one
cap (<Key>⌘C</Key> as a single chip); that is how a wide ⌘C chip ends up next
to a small lone ⌫. The menu shortcut wrappers split their strings automatically
via the exported splitKeyRun("⌘⇧S") → ["⌘", "⇧", "S"]; use it anywhere else a
combined string arrives.
Accessibility
-
Each keycap carries its own accessible name.
<kbd>is a generic role, which forbidsaria-label, so the name is real text: the visible form isaria-hiddenand ansr-onlytwin alongside it is what gets announced. A cap readingCtrlannounces "Control"; ⌘ announces "Command". -
Symbols you pass yourself are named too, including mixed runs —
<Key>⌘S</Key>announces "Command S" rather than a stray character. -
Separators are
aria-hidden; a shortcut reads as its keys, in order. -
Shortcut binds nothing. It is a hint. Put the binding on the control and declare it with
aria-keyshortcuts, whose syntax is its own (Meta+K, space-separated for alternatives):<button aria-keyshortcuts="Meta+K" onClick={open}> Search <Shortcut keys={["mod", "k"]} glyphs aria-hidden /> </button> -
When the hint repeats something already announced — a menu item's own shortcut, a search field's placeholder — mark the hint
aria-hiddenso it is not read twice. -
Not interactive: it takes no focus and has no pointer target, so WCAG 2.5.5's 44px minimum does not apply. The size scale follows the text it sits beside.