Kbd
Free · MITKeyboard keys and combos in flat, raised 3D, outline, glass, neo and solid styles. Keycaps depress when the real keys are pressed, Mod maps to ⌘ or Ctrl, and onTrigger turns any combo or G-then-T sequence into a working hotkey.
Installation
$ npx shadcn@latest add https://goodui.dev/r/kbd.jsonThe file lands in components/goodui/kbd.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/kbd works too.
Usage
import { useRouter } from "next/navigation"
import { Search } from "lucide-react"
import { Kbd } from "@/components/goodui/kbd"
export function SearchTrigger({ onOpen }: { onOpen: () => void }) {
const router = useRouter()
return (
<div className="flex items-center gap-4">
<button type="button" onClick={onOpen} className="flex w-64 items-center gap-2 rounded-lg border px-3 py-2 text-sm text-muted-foreground">
<Search className="size-4" />
Search clients and bookings
<Kbd keys="Mod+K" size="xs" variant="flat" className="ms-auto" onTrigger={onOpen} />
</button>
<span className="flex items-center gap-2 text-xs text-muted-foreground">
Go to calendar <Kbd keys={["G", "C"]} separator="then" onTrigger={() => router.push("/calendar")} />
</span>
</div>
)
}When to use
- Showing a shortcut next to a menu item, button or search box: ⌘K, Ctrl+S, Esc.
- Shortcut cheat sheets and onboarding tips, with keycaps that press down when the real keys do.
- Wiring the shortcut you display at the same time: onTrigger turns the same combo into a working hotkey, including G-then-T sequences.
- A full command menu: use command-palette-morph and show its shortcut with Kbd.
- Inline code or identifiers (API keys, file names): use text with variant="code".
- Status or count labels: use badge.
| If you need | Use |
|---|---|
| Hints inside menus and inputs | variant="flat" or "outline", size="xs", animation="none" |
| Docs and cheat sheets | variant="raised" (default) with animation="press" |
| Branded or highlighted shortcut | variant="solid" with a tone, or animation="glow" |
| On images or gradients | variant="glass" |
| Playful or retro brands | variant="neo" |
| Two-key sequences like G then D | separator="then" with onTrigger |
| Words instead of symbols (Shift, Enter, Cmd) | symbols={false} |
Examples
Cheat sheet row
<div className="flex items-center justify-between text-sm">
<span>New booking</span>
<Kbd keys="Mod+Shift+B" size="md" />
</div>Single key
<Kbd variant="outline" size="xs" animation="none">Esc</Kbd>Translated screen-reader names
<Kbd
keys={["G", "T"]}
separator="then"
labels={{ then: "dann", keyName: (name) => (name === "Command" ? "Befehl" : name === "Control" ? "Steuerung" : name) }}
/>API reference
| Prop | Type | Default |
|---|---|---|
| keys Keys of the combo: ["Mod", "Shift", "P"] or "Mod+Shift+P". | string[] | string | — |
| children Single key content, used when `keys` is not set. | React.ReactNode | — |
| variant Keycap style: flat, raised 3D (default), outline, glass, neo or solid. | "flat" | "raised" | "outline" | "glass" | "neo" | "solid" | "raised" |
| tone Keycap accent color; neutral (default) keeps it gray. | "primary" | "neutral" | "success" | "warning" | "danger" | "info" | "neutral" |
| size | "xs" | "sm" | "md" | "lg" | "sm" |
| shape | "square" | "rounded" | "pill" | "rounded" |
| separator Between keys: plus (a combo), none, or then (a sequence; onTrigger then waits for the keys in order). | "plus" | "none" | "then" | "plus" |
| font Keycap font; mono uses --font-mono. | "sans" | "mono" | "sans" |
| symbols Show ⌘ ⇧ ⌥ ↵ ⌫ arrows instead of words. | boolean | true |
| animation press depresses a keycap while its real key is held; glow adds a tone glow. none adds no key listeners. | "none" | "press" | "glow" | "press" |
| onTrigger Fires when every key of the combo is down (or, with separator="then", when the sequence is typed). | (e: KeyboardEvent) => void | — |
| preventDefault Prevent the browser default when onTrigger fires (e.g. Ctrl+K focusing the address bar). | boolean | true |
| allowInInputs Fire in text fields too. By default combos without Mod/Ctrl/Cmd/Alt are ignored while typing. | boolean | false |
| labels Text for built-in controls and screen readers; pass translations here. | Partial<KbdLabels> | — |
Also accepts every <kbd> attribute.
Accessibility
| The keys you pass | With animation="press" or "glow" (or onTrigger), each keycap reacts while its real key is held. Listeners are on window, so focus can be anywhere. |
| The full combo (e.g. Mod+K) | Calls onTrigger once when every key is down (not on auto-repeat) and prevents the browser default unless preventDefault={false}. |
| Sequences (separator="then") | Calls onTrigger when the keys are typed in order with less than 1.2 s between them; modifier presses are ignored. |
- Renders a <kbd> wrapping one <kbd> per key, the HTML pattern for key combinations.
- Symbols (⌘ ⇧ ⌥ ↵ ↑…) are aria-hidden and paired with visually hidden names ("Command", "Shift", "Up arrow"). Translate them with labels.keyName; labels.then translates the visible "then".
- The + and "then" separators are aria-hidden.
- Single-key and Shift-only hotkeys are ignored while typing in inputs, textareas, selects and contenteditable, so they do not steal text input. Combos with Mod, Ctrl, Cmd or Alt still fire; allowInInputs makes all of them fire.
Motion
- press (default): a keycap depresses for as long as its key is held, in 90 ms. raised and solid move down 2px and lose their bottom edge, neo slides into its shadow, flat, outline and glass shrink to 0.92 and tint.
- glow: the held keycap also gets a tone-colored ring and glow.
- Reduced motion: keycaps do not move; only the background color changes while a key is held.
- Every Kbd with animation other than "none", or with onTrigger, adds keydown, keyup and blur listeners on window. In long static lists (menus with 20 shortcuts), use animation="none" and keep onTrigger to the ones you need.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Write "Mod" for the platform modifier: it shows ⌘ on Mac and Ctrl elsewhere and matches Meta or Control. keys accepts "Mod+Shift+P" or ["Mod", "Shift", "P"]; "Ctrl++" means the plus key.
- Use children only for a single key without keys. Do not render your own + between Kbd elements; pass all keys to one Kbd.
- onTrigger replaces a separate hotkey hook for that shortcut; do not register the same combo twice. Set preventDefault={false} if the browser action should still happen.
- The platform is detected after mount, so the first server render shows Ctrl; that is expected.
- Default is variant="raised" tone="neutral" size="sm" symbols animation="press".
FAQ
Can it replace a hotkeys library?
For shortcuts that are also shown on screen, yes: onTrigger handles combos, Mac/Windows modifiers, auto-repeat, inputs and G-then-T sequences. It only listens while the Kbd is mounted.
Does it work on Mac and Windows?
Yes. Mod shows ⌘ on Mac (detected from the platform after mount) and Ctrl elsewhere, and Alt shows ⌥ on Mac. Releasing any modifier or leaving the window releases every keycap, so none get stuck after ⌘+Tab.
Related
A search field morphs into a command palette through a shared layout animation, with fuzzy matching, highlighted letters, grouped results, a sliding selection, arrow keys, Enter, Esc and Ctrl/Cmd+K.
Every body-copy role in one primitive: paragraph, lead, small, caption, overline, eyebrow pill, quote with attribution and inline code, with tones, readable measures, truncate or line clamp, and on-view entrances.
One badge, every layer: solid, soft, outline, status dot, gradient, glass and neo in six tones, with pulse, shine, bounce-in and count-pop animations, icons, removable chips and clickable filters.
One button with ten variants, six tones, seven shadows, ten animations, three shapes and five sizes that all combine, plus loading and icon slots.