Kbd

Displays a keyboard key or a combination of keys.

⌘⇧⌥⌃Ctrl+B
import * as stylex from "@stylexjs/stylex"
import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { VStack } from "@/components/ui/stack"
import { color, leading, text } from "@/theme/tokens.stylex"

const styles = stylex.create({
  plus: { color: color.mutedForeground, fontSize: text.xs, lineHeight: leading.xs },
})

export default function KbdDemo() {
  return (
    <VStack gap={4} align="center">
      <KbdGroup>
        <Kbd>⌘</Kbd>
        <Kbd>⇧</Kbd>
        <Kbd>⌥</Kbd>
        <Kbd>⌃</Kbd>
      </KbdGroup>
      <KbdGroup>
        <Kbd>Ctrl</Kbd>
        <span {...stylex.props(styles.plus)}>+</span>
        <Kbd>B</Kbd>
      </KbdGroup>
    </VStack>
  )
}

Installation

Install the following dependencies:

pnpm add @base-ui/react @stylexjs/stylex clsx

Set up the token layer and the StyleX compiler, if you have not already. See Installation, and paste states.ts from the same page. The keycap's surface comes from it. Kbd also needs interop.css, which sizes an icon inside a key.

Copy and paste the following code into your project.

components/ui/kbd.tsx
"use client"

import { useRender } from "@base-ui/react/use-render"
import * as stylex from "@stylexjs/stylex"
import type { StyleXStyles } from "@stylexjs/stylex"
import { clsx } from "clsx"

import { color, font, leading, radius, space, text } from "@/theme/tokens.stylex"

const styles = stylex.create({
  kbd: {
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    flexShrink: 0,
    gap: space.s0_5,
    height: space.s5,
    minWidth: space.s5,
    paddingInline: space.s1,
    borderRadius: radius.sm,
    borderWidth: 1,
    borderStyle: "solid",
    borderColor: color.border,
    backgroundColor: color.muted,
    color: color.mutedForeground,
    fontFamily: font.mono,
    fontSize: text.xs,
    lineHeight: leading.xs,
    fontWeight: 500,
    whiteSpace: "nowrap",
    userSelect: "none",
    "--carbon-icon-size": space.s3,
  },
  group: {
    display: "inline-flex",
    alignItems: "center",
    gap: space.s1,
  },
})

const kbdStyles = {
  base: styles.kbd,
  group: styles.group,
}

type Overrides = { xstyle?: StyleXStyles; className?: string }

type KbdProps = useRender.ComponentProps<"kbd"> & Overrides

function Kbd({ render, ref, style, xstyle, className, ...props }: KbdProps) {
  const sx = stylex.props(styles.kbd, xstyle)
  return useRender({
    defaultTagName: "kbd",
    render,
    ref,
    props: {
      "data-slot": "kbd",
      ...sx,
      ...props,
      className: clsx("carbon-icons", sx.className, className),
      style: { ...sx.style, ...style },
    },
  })
}

function KbdGroup({ render, ref, style, xstyle, className, ...props }: KbdProps) {
  const sx = stylex.props(styles.group, xstyle)
  return useRender({
    defaultTagName: "kbd",
    render,
    ref,
    props: {
      "data-slot": "kbd-group",
      ...sx,
      ...props,
      className: clsx(sx.className, className),
      style: { ...sx.style, ...style },
    },
  })
}

export { Kbd, KbdGroup, kbdStyles }

Update the import paths to match your project setup.

Usage

import { Kbd, KbdGroup } from "@/components/ui/kbd"
<KbdGroup>
  <Kbd>⌘</Kbd>
  <Kbd>K</Kbd>
</KbdGroup>

Group

Wrap keys pressed together in KbdGroup. It renders a kbd of kbds, which is how HTML marks up a combination, and sits inline in running text.

Use ⌘K to open the command palette, or Esc to close it.

import * as stylex from "@stylexjs/stylex"
import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { color, leading, text } from "@/theme/tokens.stylex"

const styles = stylex.create({
  text: { color: color.mutedForeground, fontSize: text.sm, lineHeight: leading.sm },
})

export default function KbdGroupDemo() {
  return (
    <p {...stylex.props(styles.text)}>
      Use{" "}
      <KbdGroup>
        <Kbd>⌘</Kbd>
        <Kbd>K</Kbd>
      </KbdGroup>{" "}
      to open the command palette, or <Kbd>Esc</Kbd> to close it.
    </p>
  )
}

Input group

Put a shortcut at the end of a search field with an InputGroupAddon.

⌘K
import * as stylex from "@stylexjs/stylex"
import { Input } from "@/components/ui/input"
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"
import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { Search } from "lucide-react"

const styles = stylex.create({
  group: { width: "100%", maxWidth: "20rem" },
})

export default function KbdInputGroup() {
  return (
    <InputGroup xstyle={styles.group}>
      <Input placeholder="Search..." aria-label="Search" />
      <InputGroupAddon>
        <Search />
      </InputGroupAddon>
      <InputGroupAddon align="inline-end">
        <KbdGroup>
          <Kbd>⌘</Kbd>
          <Kbd>K</Kbd>
        </KbdGroup>
      </InputGroupAddon>
    </InputGroup>
  )
}

API reference

Pass render to replace a part's element or compose it with another component.

Both parts render a kbd through Base UI's useRender, and take xstyle, className and inline style alongside every kbd attribute.

Kbd

PropTypeDefault
renderReactElement | ((props, state) => ReactElement)<kbd />
xstyleStyleXStyles—
classNamestring—

A flat keycap, 1.25rem tall and at least as wide, in the monospace face. It is not focusable.

KbdGroup

An inline row of keys with a 0.25rem gap. Text between keys, such as a +, sits in the same row.

kbdStyles

The style maps behind the component. Spread them onto any element.

KeyWhat it is
baseThe cap: fill, hairline, size and type
groupThe inline row

Data attributes

AttributeOnEffect
data-slot="kbd"a keyTargets a key from a parent
data-slot="kbd-group"the groupTargets the group from a parent

CSS variables

VariableOnEffect
--carbon-icon-sizea keyThe icon's size, read by interop.css. Defaults to 12px