FoundationsKbd

An inline key-cap for keyboard shortcuts.

Preview

press Ctrl K to toggle devtools

The thinking

Shortcut text needs to read as a physical key, not as bold or monospace styling, so the reader knows it's something to press rather than something to read. The one decision the whole thing rests on is that the cap stands in for a physical key, not for emphasis: a reader scanning a sentence should be able to stop scanning and reach for the keyboard without having to parse which characters are the instruction. So the cap is a plain <kbd> that borrows the small cues a real key carries — it sits slightly proud of its surface and its bottom edge reads heavier than the rest. The detail I care about most is that it never asks the surrounding text to make room for it: h-5 and text-xs are rem-based, so the cap follows the reader's font-size setting instead of a pixel value I froze, and the label sits in font-mono because a key name is input, not prose. A cap that scales with the reader stays part of the sentence it sits in. Widgets don't.

Interface design

A physical key has a shadow side, so the bottom border is 2px while the other three stay 1px — border-b-2 layered over a plain border. I render that edge as border, not box-shadow, because a border occupies layout: the cap is exactly as tall as it looks, and nothing paints outside the element to collide with line-height in running text. The surface is bg-secondary, the same step the site uses for anything resting just above the page, and the label is text-muted-foreground — a step quieter than the sentence around it, so the cap reads as an object in the text rather than emphasis. rounded-sm keeps the corners tight enough to stay a key-cap and not a pill.

Interaction

Nothing here moves, and that's the interaction design. The class list carries no transition, no active: state, no cursor change, because motion on hover or press would signal that the cap itself is operable — and it isn't. The key it depicts is on the reader's desk. There is no intent to infer either: a cursor passing over documentation is reading, not reaching, so any forgiveness logic would be answering a question nobody asked. A cap that flinched under the pointer would be promising an affordance that doesn't exist. It holds still.

Sound design

The cap is silent, and not by an opt-out. The global SoundLayer speaks through one capturing click listener that matches interactive roles — a[href], button, the Radix menu and toggle roles — and a <kbd> matches none of them, so the layer passes right over it. It doesn't need data-silent either; that marker exists for components that own a paired sound the layer would double-fire over, and this one owns nothing. Markup without a role doesn't speak, which is the system working as designed — the thing that makes a sound when you press Ctrl K is your keyboard.

Empathy

There's no disabled state, no focus ring, and no reduced-motion branch, because there is nothing to operate, focus, or animate — the element never enters the tab order and no motion exists to reduce. What it does carry is the semantics: this renders a real <kbd>, the HTML element for keyboard input, so the meaning lives in the markup and survives anywhere the styling doesn't. Content gets the two guarantees the classes actually make: min-w-5 keeps a lone K a proper square cap instead of a sliver, and px-1.5 lets Ctrl or Shift widen the cap to fit — nothing truncates, so a long label stretches the key rather than losing characters. Everything else, aria attributes included, spreads straight through props onto the element. (A component this small mostly has to know what not to do.)
kbd.tsx
import * as React from "react";

import { cn } from "@/lib/utils";

export interface KbdProps extends React.HTMLAttributes<HTMLElement> {
	ref?: React.Ref<HTMLElement>;
}

// the 2px bottom edge is the key-cap's shadow side rendered as border:
// it occupies layout instead of floating on a box-shadow, so the cap is
// exactly as tall as it looks inside running text
const Kbd = ({ ref, className, ...props }: KbdProps) => (
	<kbd
		ref={ref}
		className={cn(
			"inline-flex h-5 min-w-5 items-center justify-center rounded-sm border border-b-2 border-border bg-secondary px-1.5 font-mono text-xs text-muted-foreground",
			className
		)}
		{...props}
	/>
);
Kbd.displayName = "Kbd";

export { Kbd };