Kbd
A keycap for a key or shortcut: key hints under a command palette, or a shortcut inside a button label.
Installation
$
npx shadcn@latest add https://ui.mattdowney.com/r/kbd.jsonUsage
import { Kbd, KbdGroup, KbdHint } from "@/components/ui/kbd"Examples
Key hints
KbdHint pairs keys with the word for what they do and owns the 6px between them; pass an array for several keys, which sit 4px apart. Show hints to pointer devices only; a phone has no keys.
↑↓Navigate↵OpenEscClose
In a button
Put the cap after the label. Inside a Button its text and edge follow the label's own colour, a step quieter, so it belongs to every variant's fill.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| childrenKbd | React.ReactNode | - | The key or keys, as glyphs: ⌘K, ↵, Esc. One cap per key in a hint row; one cap for a whole chord in a button. |
| keysKbdHint | string | string[] | - | One key, or several shown as separate caps in a KbdGroup. |
| childrenKbdHint | React.ReactNode | - | The word for what the keys do: Navigate, Open, Close. |
| childrenKbdGroup | React.ReactNode | - | Several Kbd caps, 4px apart, read as one hint. KbdHint builds this for you from an array. |
childrenKbdReact.ReactNodeThe key or keys, as glyphs: ⌘K, ↵, Esc. One cap per key in a hint row; one cap for a whole chord in a button. |
keysKbdHintstring | string[]One key, or several shown as separate caps in a KbdGroup. |
| childrenKbdHintReact.ReactNodeThe word for what the keys do: Navigate, Open, Close. |
childrenKbdGroupReact.ReactNodeSeveral Kbd caps, 4px apart, read as one hint. KbdHint builds this for you from an array. |
import * as React from "react"
import { cn } from "@/registry/lib/utils"
// @use-when a key or shortcut shown as a keycap: a hint row under a command
// palette (↑↓ Navigate, ↵ Open, Esc Close), or the shortcut inside a button's
// label (Search ⌘K). Not CommandShortcut, which is a muted hint at the end of
// a palette row.
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<kbd
data-slot="kbd"
className={cn(
// A hairline ring, not a border: the cap is an indicator, so it takes
// Badge's flat edge and no drop shadow. `font-sans` because browsers
// set <kbd> in monospace, and ⌘ and ↵ are drawn in the UI face.
"pointer-events-none inline-flex h-5 min-w-5 shrink-0 items-center justify-center gap-0.5 rounded-sm px-1 font-sans text-2xs leading-none font-medium text-muted-foreground shadow-ring-flat select-none",
// Inside a Button the cap follows the label's own colour, text and
// edge both, so it belongs to every variant's fill instead of laying a
// neutral grey over an accent one. Text at 80% of the label keeps 3:1
// on the weakest fill (`soft`: the label is 4.2:1, the cap 3.2:1); 70%
// fell to 2.7. The edge is a 1px ring at 40% of that, read only as the
// cap's shape, so it carries no contrast duty.
"in-data-[slot=button]:text-current/80 in-data-[slot=button]:shadow-none in-data-[slot=button]:ring-1 in-data-[slot=button]:ring-current/40",
className
)}
{...props}
/>
)
}
// Several keys pressed or offered together, ↑ ↓ for Navigate: one cap per key,
// 4px apart, the tightest gap the kit's control rows use (Button xs and sm), so
// the keys read as one unit and stay closer to each other than to their word.
function KbdGroup({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="kbd-group"
className={cn("inline-flex items-center gap-1", className)}
{...props}
/>
)
}
// A key hint: keys, then the word for what they do (↵ Open). The gap is 6px,
// Button's icon-to-label gap at the label size this sits at (text-xs, Button's
// default), because a keycap beside its word is the same pairing. A space
// character is not a gap: it measures differently in every font.
function KbdHint({
keys,
className,
children,
...props
}: React.ComponentProps<"span"> & {
/** One key, or several shown as separate caps. */
keys: string | string[]
}) {
return (
<span
data-slot="kbd-hint"
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
>
{Array.isArray(keys) ? (
<KbdGroup>
{keys.map((key) => (
<Kbd key={key}>{key}</Kbd>
))}
</KbdGroup>
) : (
<Kbd>{keys}</Kbd>
)}
{children}
</span>
)
}
export { Kbd, KbdGroup, KbdHint }