Skip to content
Drivn
7 min read

React Kbd Component Examples

Drop-in React Kbd examples: single keys, Mac ⌘K shortcuts, outline variant, inline hints, palette triggers, and tooltip cues. Zero runtime deps, TypeScript.

A shortcut hint is one of the smallest things you ship and one of the easiest to get subtly wrong — the glyph sits a pixel off the baseline, a single letter renders as a lopsided rectangle, the chord keys drift apart. The Kbd component in Drivn is a thin wrapper around the native HTML <kbd> element that settles those details once. Its styles.base sets h-5 with w-fit min-w-5, so a lone K stays square while a longer label like Shift grows sideways, then rounded-sm px-1 for the pill shape and font-mono text-xs font-medium for the key face. A pointer-events-none select-none pair keeps the element non-interactive, which matches how a physical key reads inside a sentence.

The file is under sixty lines of TypeScript with zero runtime UI dependencies. It exposes two variants — default and outline — and a Kbd.Group static property attached through Object.assign(KbdRoot, { Group }) for chord sequences like ⌘ + K. One rule in styles.base does more work than the rest combined: [&_svg:not([class*=size-])]:size-3 auto-sizes any unsized SVG descendant to twelve pixels, so a Lucide <Command /> drops in as a Kbd child with no className at all.

The examples below cover a basic single key, both variants, a Mac ⌘K chord built with Kbd.Group, a three-key Windows chord, an inline hint inside body text, and a Command palette trigger wired to a real keydown listener. Every snippet uses the @/components/ui/kbd import path the Drivn CLI installs under, so it compiles the moment you run npx drivn add kbd. Every snippet is TypeScript — Drivn ships TypeScript-only.

Basic single key

The minimum viable Kbd is one element with the key label as children. Drop the component into JSX, pass a string like "Shift" or "Esc", and the rendered output is a small pill at h-5 (twenty pixels tall) with a bg-muted background, text-muted-foreground foreground, font-mono family, and text-xs size. The pointer-events-none and select-none rules in styles.base make the key non-interactive and unselectable, which matches how a physical keyboard key behaves visually inside body text.

The default variant uses the filled bg-muted text-muted-foreground recipe. There is no onClick handler on the Kbd itself — keyboard shortcuts trigger from a parent listener (a useEffect with window.addEventListener("keydown", ...)), and the Kbd is purely the visual hint that tells the user what to press. Pair single Kbd elements inline with body copy when you describe a one-key shortcut like Esc to close a Dialog.

1import { Kbd } from "@/components/ui/kbd"
2
3export default function Page() {
4 return (
5 <Kbd>Shift</Kbd>
6 )
7}

Default and outline variants

The variant prop takes default or outline, and both entries live in a styles.variants object directly below styles.base. The default value is bg-muted text-muted-foreground — a filled pill that holds its own against a plain page background. The outline value is border border-border text-muted-foreground — a hairline edge with no fill that inherits whatever surface sits behind it. The choice is about layering. On a bare background the filled pill reads cleanly, but inside an already-tinted container — a Tooltip bubble, a Toast, a Card header, a search trigger with its own muted background — a second fill flattens into a patch and the outline stays legible.

The typing comes from the styles object rather than a hand-written union: the prop is declared as variant?: keyof typeof styles.variants. Autocomplete offers exactly the two valid strings, and variant="solid" is a compile error rather than a silently ignored prop. Because the type is derived, adding a third variant after install is a single edit — add a key to styles.variants in your local @/components/ui/kbd file and the prop type picks it up with no second change. Both variants share every rule in styles.base, so height, padding, radius, and the SVG auto-size rule stay consistent whichever you pass.

1import { Kbd } from "@/components/ui/kbd"
2
3export default function Page() {
4 return (
5 <div className="flex items-center gap-2">
6 <Kbd variant="default">Shift</Kbd>
7 <Kbd variant="outline">Shift</Kbd>
8 </div>
9 )
10}

Mac shortcut: ⌘ + K with Kbd.Group

A two-key chord like ⌘ + K renders as two <Kbd> elements nested inside a <Kbd.Group> wrapper. The Group primitive is exposed via Object.assign(KbdRoot, { Group }) so the import is a single name — Kbd — and the call site reads as one parent with two children. The Group element applies inline-flex items-center gap-1 so the keys sit on one line with a four-pixel gap between them, matching the visual rhythm of a real keyboard.

The Command glyph comes from lucide-react as the Command icon. Drop it inside the first Kbd as a child — Drivn's styles.base includes the rule [&_svg:not([class*=size-])]:size-3 so the icon auto-sizes to twelve pixels without any className on the icon. The second Kbd holds the letter "K". The result is the standard ⌘K chord you see next to a search input at the top of every modern app. Pair this with the Command component to open a palette on the key combo.

1import { Kbd } from "@/components/ui/kbd"
2import { Command } from "lucide-react"
3
4export default function Page() {
5 return (
6 <Kbd.Group>
7 <Kbd><Command /></Kbd>
8 <Kbd>K</Kbd>
9 </Kbd.Group>
10 )
11}

Windows shortcut: Ctrl + Shift + P

For three-key chords like the VS Code command palette trigger Ctrl + Shift + P, nest three Kbd elements inside one Kbd.Group. The gap-1 between siblings handles the spacing — there are no explicit + separators because the inline-flex layout reads as a sequence visually. If you want a literal + glyph between keys for typographic clarity, drop a <span className="text-muted-foreground">+</span> between the Kbd elements, but most product UIs skip it because the spacing already communicates the chord.

The Group renders all three keys as default variant pills. To match a documentation aesthetic that prefers thin outlines, pass variant="outline" on each Kbd inside the Group — the variants compose freely because the prop sits on each Kbd individually, not on the Group wrapper. The full chord renders in seven lines of JSX with one import.

1import { Kbd } from "@/components/ui/kbd"
2
3export default function Page() {
4 return (
5 <Kbd.Group>
6 <Kbd>Ctrl</Kbd>
7 <Kbd>Shift</Kbd>
8 <Kbd>P</Kbd>
9 </Kbd.Group>
10 )
11}

Inline shortcut hint in body text

A common pattern is a sentence that ends with a chord — "Press ⌘K to open the search dialog." The Kbd Group nests inside a <p> tag with surrounding text on either side, separated by {' '} JSX whitespace tokens so the spaces survive Prettier. The Kbd sits at h-5 and align-items: center from the inline-flex on the Group, which puts the key centered on the text baseline of the paragraph.

The inline pattern is the most common shortcut hint in product UIs — settings pages, onboarding tooltips, empty states. Pair it with the Tooltip component for a hover hint, or with the Toast component for a transient confirmation that includes a shortcut to undo. The Kbd reads as part of the sentence because the font-mono family contrasts cleanly with the font-sans body text without breaking the line height.

1import { Kbd } from "@/components/ui/kbd"
2import { Command } from "lucide-react"
3
4export default function Page() {
5 return (
6 <p className="text-sm text-muted-foreground">
7 Press{" "}
8 <Kbd.Group>
9 <Kbd><Command /></Kbd>
10 <Kbd>K</Kbd>
11 </Kbd.Group>
12 {" "}to open the search dialog.
13 </p>
14 )
15}

Command palette trigger with keyboard listener

A real ⌘K shortcut needs a useEffect that listens for the key combination and opens the palette. The pattern is a keydown listener on window that checks event.metaKey && event.key === "k", calls event.preventDefault() to stop the browser default (locating the search bar), and toggles a useState boolean that controls the Command palette's open state. Pair the Kbd visually next to the search input that opens the palette so the user sees what to press.

The listener runs only on the client, so the component file needs "use client" at the top. Clean up the listener in the useEffect return so it does not stack across re-renders. The same pattern handles Ctrl + K on Windows and Linux by checking event.ctrlKey instead of event.metaKey, or both with (event.metaKey || event.ctrlKey) if you want one shortcut that works across platforms. See the Command component reference for the full palette implementation.

1"use client"
2import { Kbd } from "@/components/ui/kbd"
3import { Command } from "lucide-react"
4import { useEffect, useState } from "react"
5
6export default function SearchTrigger() {
7 const [open, setOpen] = useState(false)
8
9 useEffect(() => {
10 function onKeyDown(event: KeyboardEvent) {
11 if ((event.metaKey || event.ctrlKey) && event.key === "k") {
12 event.preventDefault()
13 setOpen((prev) => !prev)
14 }
15 }
16 window.addEventListener("keydown", onKeyDown)
17 return () => window.removeEventListener("keydown", onKeyDown)
18 }, [])
19
20 return (
21 <button
22 onClick={() => setOpen(true)}
23 className="inline-flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm"
24 >
25 <span className="text-muted-foreground">Search...</span>
26 <Kbd.Group>
27 <Kbd variant="outline"><Command /></Kbd>
28 <Kbd variant="outline">K</Kbd>
29 </Kbd.Group>
30 </button>
31 )
32}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

Import the Command icon from lucide-react and drop it directly inside the Kbd as the child — no className, no wrapper. Drivn's Kbd styles.base includes the rule [&_svg:not([class*=size-])]:size-3 so any unsized SVG descendant auto-sizes to twelve pixels. The icon fits the twenty-pixel-tall Kbd pill without manual sizing. To override the size, pass an explicit className="size-4" on the icon and the not() matcher in the rule excludes it, letting your explicit class win.

Use variant="outline" when the Kbd sits inside a surface that already carries a muted fill — a Tooltip bubble, a Toast container, a Card header, a search trigger with a tinted background. The default bg-muted would visually compete with the surrounding fill and read as a flat patch. The outline variant uses border border-border for a hairline edge and inherits the surrounding background, which keeps the key readable without adding a second fill layer.

Yes — the Group is a layout primitive with inline-flex items-center gap-1 and no maximum-children rule. Chord sequences with four or more keys like Ctrl + Alt + Shift + S render the same way: drop the Kbd elements one after another inside the Group. The gap between siblings handles spacing automatically. For documentation that shows a sequence of separate shortcuts (press X, then Y), use two separate Groups separated by prose rather than one long Group, because the gap inside a single Group reads as a simultaneous chord rather than a sequence.

No — the Kbd is purely the visual hint. The component renders a styled <kbd> element and has no event listeners attached. To wire a real shortcut, add a useEffect in the parent component that calls window.addEventListener("keydown", handler) and checks event.metaKey, event.ctrlKey, and event.key against the chord you want to trigger. Pair the listener with setOpen state for a Command palette or any other shortcut behavior. The Kbd just tells the user what to press.

Yes. The Kbd file has no "use client" directive because it uses no client-only APIs — no useState, no useEffect, no event handlers in the component body. You can render Kbd elements inside a Server Component as static shortcut hints in documentation, marketing pages, or layout headers. Only the surrounding palette logic (the keydown listener) needs the client boundary, and that lives in its own Client Component. Next.js draws the boundary based on the consumer file, so Kbd works in both rendering modes without configuration.