Skip to content
Navigation

Kbd

A keycap for a keyboard shortcut. Use KbdGroup for a combination — the grouping is semantic, not decorative.

Added 14 October 2026

Grouping changes the meaning

Per the HTML Standard, sibling <kbd> elements mean keys pressed in sequence, while a <kbd> wrapping <kbd>s means a combination. KbdGroup is that outer <kbd>, so this is not a styling wrapper you can skip:

<KbdGroup><Kbd>⌘</Kbd><Kbd>K</Kbd></KbdGroup>   // press ⌘ and K together
<Kbd>G</Kbd> <Kbd>I</Kbd>                        // press G, then I

Pick one convention for the + and hold to it. This library’s demos omit it inside a group, because the group already carries the meaning and the glyphs read fine without it.

aria-label does not work here — and cannot

<kbd> maps to the ARIA generic role, and name-from-author is prohibited for that role. An aria-label on a Kbd or KbdGroup is specified to be ignored, so it is not a fix, it is a lie in the source. [a11y]

Consequence: ⌘ (U+2318) and ⇧ (U+21E7) have no reliable screen-reader pronunciation, and you cannot label your way out of it. Two real options:

  1. Use words — Cmd, Shift, Ctrl, Alt. Least clever, most robust, and the right default for anything a user has to act on.
  2. Pair a hidden word with the glyph — the glyph aria-hidden, a visually hidden word beside it:
<Kbd><span aria-hidden="true">⌘</span><span className="sr-only">Command</span></Kbd>

Reach for the glyph-only form only in dense UI where the shortcut is a reminder rather than instruction.

Where a keycap belongs

  • Beside the action it triggers — in a DropdownMenu row, a Tooltip, a command palette row, or inline in a short instruction.
  • Never as a control. A keycap is a label; it is pointer-events-none by design. If it should be clickable it is a Button.

Inside a tooltip the keycap inverts automatically, because the tooltip surface is inverted. That override keys off different attributes per framework (React on the tooltip’s data-slot, Angular on role="tooltip", which is what brain emits) — mentioned only so nobody “aligns” them.

Deviation from upstream

The text is text-foreground, not upstream’s text-muted-foreground: on the muted keycap fill that computes to 3.93:1 in the light theme, and at 12px it needs 4.5:1 (WCAG 1.4.3). A keycap is a label, not secondary text. Both shadcn and spartan helm fail this pairing; don’t restore it.

font-sans is also deliberate. This library reserves font-mono for machine data — prices, IDs, codes. A keycap names a physical key, so mono would make Ctrl/Enter read as code.

Never

  • A bare glyph keycap in instructional copy — write the word.
  • aria-label on a keycap; it is ignored.
  • Sibling keycaps for a simultaneous shortcut; that means “then”.
  • A keycap as a button.