Display
Kbd
Keyboard keycap and key-combination display, rendered as real `<kbd>` markup with platform-aware glyphs for mod/cmd/ctrl/alt.
Preview in your theme
Loading preview…
- Registry dependencies
- utils
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/kbd.jsonPrompt
The prompt behind this component — paste it into your AI assistant to recreate or adapt it.
Build a React + TypeScript + Tailwind "Kbd" / "KbdCombo" pair for displaying
keyboard shortcuts.
Contract
- Kbd: forwardRef<HTMLElement, KbdProps> rendering a real <kbd> element.
extends React.HTMLAttributes<HTMLElement>. Props: size?: "sm" | "md"
(default "md"); platform?: "mac" | "win" (forces modifier glyphs; omit to
auto-detect from the browser). className merged via cn(), rest props spread
on the <kbd>.
- KbdCombo: forwardRef<HTMLSpanElement, KbdComboProps> rendering a <span> of
Kbd keycaps. Props: keys: string[] (semantic key names in press order, e.g.
["mod", "K"]); separator?: ReactNode (default "+"), rendered between
keycaps; size and platform forwarded to every Kbd.
Behavior
- Both accept semantic key names, not just literal glyphs. When a Kbd's
string child matches a known name it resolves to a glyph; anything else
(a plain letter like "K", or an already-resolved symbol like "⌘") renders
unchanged. Matching is case-insensitive.
- Two lookup tables: a platform-aware one (mod → ⌘ on mac / Ctrl on win; cmd →
⌘ / Win; ctrl → ⌃ / Ctrl; alt/option → ⌥ / Alt) and a platform-independent
one (shift → ⇧, enter/return → ↵, esc/escape → Esc, tab → ⇥, backspace → ⌫,
delete → ⌦, capslock → ⇪, space → Space, arrow keys → ↑↓←→).
- Every table entry is a { label, spoken } pair (⌘ / "Command", ⌥ / "Option",
⇧ / "Shift", ↵ / "Enter", Ctrl / "Control", Esc / "Escape", ↑ / "Arrow Up"
…). A screen reader reading ⌘ ⇧ P raw says symbol names or nothing, so when
the glyph differs from the spoken name the <kbd> holds the glyph in an
aria-hidden span plus the name in an sr-only span. Unmapped children keep
rendering as-is.
- Platform detection is SSR-safe: read via useSyncExternalStore with a
getServerSnapshot that always returns "win", and a client snapshot that
checks navigator.platform / navigator.userAgent for "mac". This renders
"win" glyphs on the server and on the client's first paint, then React
re-renders once with the real client value — no hydration mismatch, no
useEffect + setState. On a mac that first paint shows the win glyph and
swaps to ⌘ one render after hydration; pass `platform` explicitly when that
swap matters. An explicit `platform` prop always wins over detection.
- KbdCombo maps each string in `keys` through a Kbd, interleaving the
separator (aria-hidden, since it's punctuation, not a key) between them.
Rendering & styling
- Semantic tokens only: bg-muted for the keycap surface, text-muted-foreground
for the glyph, border (the theme's default border color) for the outline.
No hardcoded colors — the keycap follows the host theme and dark mode.
- Keycap: rounded-md border with a doubled bottom border (border border-b-2)
to read as a physical cap with depth, inline-flex centered content,
align-middle so it sits correctly inside a text line, select-none since a
keycap glyph isn't meant to be selected/copied as text.
- Two sizes: sm (h-5, text-[10px]) for inline-in-text use, md (h-6, text-xs)
as the default for standalone hints. No animation.
Customization levers
- Sizing: sm/md cover inline vs standalone; add a documented lg entry to
SIZE_CLASSES if a settings page needs a bigger keycap — don't invent a
one-off className override at every call site.
- Light/dark contrast: bg-muted / text-muted-foreground / border already
swap with the theme; only touch this if a surface needs a stronger keycap
(e.g. bg-secondary) for contrast against a busy background.
- Mapping table: PLATFORM_SYMBOLS and KEY_SYMBOLS are the single source of
truth — add an entry for a key name your product uses often (e.g. a
"windows" logo glyph, with its spoken name) instead of hardcoding the glyph
at each call site.
- Tooltip pairing: Kbd/KbdCombo are display-only; wrap one in a shadcn
Tooltip trigger to show "Command Palette" text alongside the shortcut, or
place it as the Tooltip's content next to an icon button.Concepts
- Semantic kbd markup — every keycap is a real
<kbd>element, not a styled<span>, so assistive tech and browser find-in-page treat it as keyboard input, not decoration. - Platform-aware modifier glyphs —
modis the portable "primary shortcut modifier": it reads as ⌘ on mac and Ctrl on windows/linux, so one prompt/one call site works everywhere instead of branching per OS at every usage. - SSR-safe client detection —
useSyncExternalStore(notuseEffect+setState) readsnavigator.platformonly on the client while giving the server a fixed snapshot, so the server-rendered HTML and the client's first paint always agree and there is no hydration warning. A mac client swaps from the win glyph to ⌘ one render after hydration; passplatformexplicitly when that swap matters. - Key-name-to-glyph mapping — a plain lookup table turns semantic names ("mod", "shift", "esc") into a glyph plus a spoken name; the glyph is what sighted users see, the spoken name is what a screen reader says ("Command", not "place of interest sign"). Unrecognized strings pass through untouched, so
<Kbd>K</Kbd>just renders "K". - Combo as separated sequence —
KbdCombois not a new visual primitive, it'sKbdrepeated with anaria-hiddenseparator between each — the combination reads correctly to screen readers as a sequence of keys, not as one run-on word.