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
"use client"

import * as React from "react"
import { cn } from "@/lib/utils"

type Platform = "mac" | "win"

/**
 * A keycap face: the glyph it shows, and the name a screen reader says in its
 * place — read raw, "⌘ ⇧ P" comes out as Unicode symbol names or as nothing.
 */
interface KeyFace {
  label: string
  spoken: string

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/kbd.json

Prompt

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 — mod is 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 (not useEffect + setState) reads navigator.platform only 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; pass platform explicitly 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 — KbdCombo is not a new visual primitive, it's Kbd repeated with an aria-hidden separator between each — the combination reads correctly to screen readers as a sequence of keys, not as one run-on word.

On This Page