DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

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/ui

Usage

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

PropTypeDefaultDescription
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"inheritedOn-color treatment for colored surfaces; inherited from <Surface> or a parent Shortcut
glyphsbooleanfalseRender Apple's modifier symbols on Apple platforms (inherits from parent Shortcut)
platform"mac" | "other"auto-detectedForce a keyboard convention instead of detecting one
classNamestring—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

PropTypeDefaultDescription
keysstring[]—Shorthand: array of key names with auto-inserted separators
childrenReactNode—Compound: Key components as children — separators auto-injected between them
separatorstring"+"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"inheritedOn-color treatment; inherited from a wrapping <Surface>
glyphsbooleanfalseRender Apple's modifier symbols on Apple platforms
platform"mac" | "other"auto-detectedForce a keyboard convention instead of detecting one
dir"ltr" | "rtl""ltr"Text direction for RTL layouts
classNamestring—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.

NameAliasesAppleWindows / LinuxAnnounced as
mod—⌘ / CmdCtrlCommand / Control
commandcmd, meta⌘ / CmdMetaCommand / Meta
controlctrl⌃ / CtrlCtrlControl
shift—⇧ / ShiftShiftShift
altoption, opt⌥ / AltAltOption / Alt
enterreturn↵ / EnterEnterReturn / Enter
escapeescEscEscEscape
tab—⇥ / TabTabTab
backspace—⌫ / BackspaceBackspaceBackspace
deletedel⌦ / DeleteDeleteDelete
spacespacebarSpaceSpaceSpace
up down left rightarrowup, …↑ ↓ ← →↑ ↓ ← →Up Arrow, …
pageup pagedownpgup, pgdn⇞ ⇟ / PgUpPgUp PgDnPage Up / Page Down
home end—Home EndHome EndHome / End
capslockcaps⇪ / CapsCapsCaps Lock
fn—FnFnFunction
f1–f19—F1F1F1

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 forbids aria-label, so the name is real text: the visible form is aria-hidden and an sr-only twin alongside it is what gets announced. A cap reading Ctrl announces "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-hidden so 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.

SelectSidebar

On this page

PlaygroundInstallationUsageExamplesDefaultSingle KeySizesRoundedSeparatorsCompoundGlyphsPlatformOn colorRTLIn a buttonIn a tooltipIn a search fieldAPI ReferenceKey propsShortcut propsKey namesOne key, one capAccessibility