How to Add a Kbd to a React App
Step-by-step guide to adding a copy-and-own keyboard-key hint to any React project with the Drivn CLI — dot-notation groups and zero runtime deps.
A keyboard-key hint is a small piece of polish that makes an app feel considered — the ⌘K badge next to a search box, the Esc chip on a dialog, the Ctrl Shift P combo in a docs page. It is display-only: a styled label that tells the reader which keys to press, with no behavior of its own. Building one is easy; building one that themes cleanly, groups combos with even spacing, and sizes an inline ⌘ glyph to match its text is the part that turns fiddly. Most teams either hand-roll a <kbd> and restyle it on every screen or pull in a whole library for one small element.
Drivn writes the component into your repository instead. The CLI copies a kbd.tsx file with two small functions — Kbd for a single key and Kbd.Group for a combo — and every class held in one styles object. It has no component dependencies, imports no icons itself, and — unusually for a UI component — no 'use client' directive, because it holds no state. The element is pointer-events-none by design, since a hint is read, never clicked. After install the file is yours to read and restyle.
This guide adds the Kbd to an existing React app in about ten minutes — install the CLI, render a single key, compose a ⌘K combo with Kbd.Group, wire a real keyboard listener, and restyle the one styles object. It works the same in Vite + React or a Next.js App Router project. For the Next.js-specific walkthrough see the Next.js Kbd guide.
Prerequisites
Before installing the Kbd, confirm your React project has the three things Drivn assumes: Tailwind CSS v4 installed and processing your CSS, TypeScript configured (the component ships as a .tsx file), and a @/ path alias pointing at your source directory. If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, all three are already wired. For a custom setup, check the compilerOptions.paths entry in tsconfig.json; the installation page lists the minimal config. Like the input and badge, the Kbd pulls in no other Drivn components and imports no icons itself — you supply icons like the Lucide Command glyph as children — so it is a single self-contained file, one of the simplest components to add first when you are trying Drivn in an existing app.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Kbd source file. The command prompts once for your install directory (defaulting to src/components/ui/), writes kbd.tsx, and finishes — there are no dependencies to resolve, so nothing else is written to your project and nothing is added to package.json. No global config file is created; the result is one TypeScript file you edit like any other component. Confirm it landed in your editor, then commit it for a clean baseline. If your project uses a monorepo layout or a non-standard source path, the CLI docs cover the flags for targeting a custom location during install.
1 # add the Kbd — a single self-contained file 2 npx drivn add kbd
Step 2 — Render a single key
Open the page where the hint should live and import Kbd from your UI directory. In its simplest form it is one element with the key label as its child: <Kbd>Esc</Kbd>. The component wraps your text in a native <kbd> styled to a compact h-5 min-w-5 pill with a font-mono text-xs font-medium label and a bg-muted fill. The variant prop switches between default (the filled chip) and outline (a bordered chip that reads lighter against dense UI), and it is typed as keyof typeof styles.variants, so your editor autocompletes the two options and a typo is a compile error. Because the element is pointer-events-none, it never intercepts a click meant for the control behind it, so you can place a hint inside a button or a menu row safely. See the Kbd docs for the full prop table.
1 import { Kbd } from '@/components/ui/kbd' 2 3 export function ShortcutHints() { 4 return ( 5 <p className="text-sm text-muted-foreground"> 6 Press <Kbd>Esc</Kbd> to close, <Kbd variant="outline">Enter</Kbd> to confirm. 7 </p> 8 ) 9 }
Step 3 — Compose a combo with Kbd.Group
Most shortcuts are combos, and Kbd.Group lays them out. It is exposed through Object.assign(KbdRoot, { Group }), so a single import gives you both the key and the group, and the call site reads as one parent with children. The Group renders an inline-flex items-center gap-1 wrapper so several Kbd elements sit together with even spacing, matching the ⌘K badge you see in Linear, Raycast, and the Drivn command search. Icons work as children: pass a Lucide Command component inside a Kbd and the base class [&_svg:not([class*=size-])]:size-3 sizes the glyph to match the text automatically, so ⌘ lines up with the K without any width or height class on the icon. This is still pure markup — the badge advertises the shortcut; the listener that acts on it comes next. For more combos see the Kbd examples.
1 import { Kbd } from '@/components/ui/kbd' 2 import { Command } from 'lucide-react' 3 4 export function SearchHint() { 5 return ( 6 <Kbd.Group> 7 <Kbd><Command /></Kbd> 8 <Kbd>K</Kbd> 9 </Kbd.Group> 10 ) 11 }
Step 4 — Wire a real listener and customize styles
The Kbd renders the hint, but it never listens for keystrokes — pointer-events-none and no event handlers means the shortcut logic is yours. Add a small 'use client' component that registers a keydown listener in a useEffect, checks for your combo (for example e.key === 'k' && (e.metaKey || e.ctrlKey)), and runs your action; render the same Kbd.Group badge inside it as the visible trigger. Styling is just as direct: every class lives in one styles object at the top of the file you own. base holds the shared shape and the icon-sizing rule, variants holds the default and outline looks, and group holds the inline-flex items-center gap-1 layout Kbd.Group uses. Edit any of them once and every hint updates; because the colors read from Tailwind tokens, changing them in theming re-themes the hints for dark and light mode.
1 'use client' 2 import { useEffect, useState } from 'react' 3 import { Kbd } from '@/components/ui/kbd' 4 import { Command } from 'lucide-react' 5 6 export function CommandTrigger() { 7 const [open, setOpen] = useState(false) 8 useEffect(() => { 9 function onKey(e: KeyboardEvent) { 10 if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { 11 e.preventDefault() 12 setOpen((v) => !v) 13 } 14 } 15 document.addEventListener('keydown', onKey) 16 return () => document.removeEventListener('keydown', onKey) 17 }, []) 18 return ( 19 <button onClick={() => setOpen(true)}> 20 Search 21 <Kbd.Group> 22 <Kbd><Command /></Kbd> 23 <Kbd>K</Kbd> 24 </Kbd.Group> 25 </button> 26 ) 27 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
Frequently asked questions
Yes. Set the project up with TypeScript and Tailwind v4 first, then run npx drivn add kbd. The Kbd has no dependency on Next.js or any router — it renders anywhere React and Tailwind reach the DOM. It pulls in no other components and no icon package (you supply icons like the Lucide Command glyph as children), so the CLI writes a single kbd.tsx file and nothing else, with nothing extra to configure after install.
No — the Kbd is display-only. It is pointer-events-none and has no event handlers, so it never reacts to keys. Add a small 'use client' component that registers a keydown listener in a useEffect, checks for your combo such as e.key === 'k' && (e.metaKey || e.ctrlKey), and runs your action. Render the Kbd.Group badge inside that island as the visible hint while the listener does the work.
Pass a Lucide component such as Command as the child of a Kbd, and the base class [&_svg:not([class*=size-])]:size-3 sizes the SVG to match the text automatically — no manual width or height class needed. This is how the ⌘K badge is built: a Kbd holding the Command icon next to a Kbd holding K, both wrapped in Kbd.Group. The icon package is yours to install; the Kbd itself imports none.
The default variant is a filled chip (bg-muted text-muted-foreground) that reads clearly on plain backgrounds like a paragraph or a card. The outline variant swaps the fill for a border (border border-border text-muted-foreground) so it stays legible against dense or busy surfaces like a toolbar. Both share the same box and monospace label; pick per placement and change either look in the styles.variants map in the file you own.
No — the Kbd source has no 'use client' directive because it holds no state and runs no effect, so a key hint renders fine inside a Server Component or a plain React tree. You add 'use client' only to the separate component you write around it — the one with the keydown listener and useState — because those hooks run in the browser. The hint itself stays a static native <kbd> either way.

