{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "kbd",
  "title": "Kbd",
  "description": "A keycap printed in a line of text, for shortcuts and docs. Symbols such as ⌘ are read aloud by name. With listen, the cap goes down when the reader presses that key on their own keyboard, so a shortcut list shows its keys working.",
  "dependencies": [
    "cn"
  ],
  "registryDependencies": [
    "https://bevel-ui.vercel.app/r/bevel.json"
  ],
  "files": [
    {
      "path": "registry/bevel/ui/kbd.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"cn\"\n\n/* Symbols people print on keycaps: the key they stand for and a name to read aloud. */\nconst symbols: Record<string, { key: string; name: string }> = {\n  \"⌘\": { key: \"Meta\", name: \"Command\" },\n  \"⌥\": { key: \"Alt\", name: \"Option\" },\n  \"⇧\": { key: \"Shift\", name: \"Shift\" },\n  \"⌃\": { key: \"Control\", name: \"Control\" },\n  \"↵\": { key: \"Enter\", name: \"Enter\" },\n  \"⏎\": { key: \"Enter\", name: \"Return\" },\n  \"⌫\": { key: \"Backspace\", name: \"Delete\" },\n  \"⎋\": { key: \"Escape\", name: \"Escape\" },\n  \"⇥\": { key: \"Tab\", name: \"Tab\" },\n  \"↑\": { key: \"ArrowUp\", name: \"Up arrow\" },\n  \"↓\": { key: \"ArrowDown\", name: \"Down arrow\" },\n  \"←\": { key: \"ArrowLeft\", name: \"Left arrow\" },\n  \"→\": { key: \"ArrowRight\", name: \"Right arrow\" },\n}\n\n/* Words people print, mapped to KeyboardEvent.key. */\nconst aliases: Record<string, string> = {\n  cmd: \"meta\",\n  command: \"meta\",\n  ctrl: \"control\",\n  option: \"alt\",\n  opt: \"alt\",\n  esc: \"escape\",\n  return: \"enter\",\n  space: \" \",\n  del: \"delete\",\n}\n\nfunction isApple() {\n  return typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent)\n}\n\n/** A key name as the store records it: lowercase KeyboardEvent.key, with mod resolved per platform. */\nfunction normalize(key: string) {\n  const lower = key.toLowerCase()\n  if (lower === \"mod\") return isApple() ? \"meta\" : \"control\"\n  return aliases[lower] ?? lower\n}\n\n/*\n * One keyboard listener for the page, shared by every listening cap. It\n * records the keys held down by both key and physical code, so ⇧K and ⌥K\n * still press the K cap.\n */\nconst held = new Set<string>()\nconst listeners = new Set<() => void>()\n\nfunction emit() {\n  for (const listener of listeners) listener()\n}\n\nfunction isTyping(target: EventTarget | null) {\n  return (\n    target instanceof Element &&\n    target.closest(\"input, textarea, select, [contenteditable='true'], [contenteditable='']\") !== null\n  )\n}\n\nfunction names(event: KeyboardEvent) {\n  const out = [event.key.toLowerCase()]\n  const code = /^(?:Key|Digit)(.)$/.exec(event.code)\n  if (code?.[1]) out.push(code[1].toLowerCase())\n  return out\n}\n\nfunction onKeyDown(event: KeyboardEvent) {\n  if (isTyping(event.target)) return\n  let changed = false\n  for (const name of names(event)) {\n    if (!held.has(name)) {\n      held.add(name)\n      changed = true\n    }\n  }\n  if (changed) emit()\n}\n\nfunction onKeyUp(event: KeyboardEvent) {\n  // macOS swallows the key-up of anything pressed with ⌘, so letting go of ⌘ lets go of everything.\n  if (event.key === \"Meta\") held.clear()\n  else for (const name of names(event)) held.delete(name)\n  emit()\n}\n\nfunction releaseAll() {\n  if (!held.size) return\n  held.clear()\n  emit()\n}\n\nfunction subscribe(listener: () => void) {\n  listeners.add(listener)\n  if (listeners.size === 1) {\n    window.addEventListener(\"keydown\", onKeyDown, true)\n    window.addEventListener(\"keyup\", onKeyUp, true)\n    window.addEventListener(\"blur\", releaseAll)\n  }\n  return () => {\n    listeners.delete(listener)\n    if (!listeners.size) {\n      window.removeEventListener(\"keydown\", onKeyDown, true)\n      window.removeEventListener(\"keyup\", onKeyUp, true)\n      window.removeEventListener(\"blur\", releaseAll)\n      held.clear()\n    }\n  }\n}\n\nconst noSubscribe = () => () => {}\n\n/** Whether `key` is held down on the keyboard right now. Undefined listens to nothing. */\nfunction useKeyHeld(key: string | undefined) {\n  const name = key === undefined ? undefined : normalize(key)\n  return React.useSyncExternalStore(\n    name === undefined ? noSubscribe : subscribe,\n    () => (name === undefined ? false : held.has(name)),\n    () => false\n  )\n}\n\ntype KbdProps = React.ComponentProps<\"kbd\"> & {\n  /**\n   * Press the cap when the reader presses the key. true listens for the key\n   * printed on it (⌘ listens for Meta); a string names the key, e.g. \"k\",\n   * \"Escape\" or \"mod\" (⌘ on Apple, Ctrl elsewhere). Ignored while typing in a field.\n   */\n  listen?: boolean | string\n}\n\n/**\n * A keycap legend for shortcuts and docs. Symbols such as ⌘ are read aloud\n * by name. With `listen`, the cap goes down when you press the real key.\n */\nfunction Kbd({ className, listen, children, ...props }: KbdProps) {\n  const symbol = typeof children === \"string\" ? symbols[children] : undefined\n  const printed = typeof children === \"string\" ? children : undefined\n  const target = listen === true ? (symbol?.key ?? printed) : listen || undefined\n  const down = useKeyHeld(target)\n  return (\n    <kbd\n      data-slot=\"kbd\"\n      data-listening={target ? \"\" : undefined}\n      data-pressed={down ? \"\" : undefined}\n      className={cn(\n        \"group/kbd inline-flex h-5 min-w-5 shrink-0 flex-col rounded-(--bevel-radius-xs) bg-(--kbd-side) pb-(--kbd-travel) align-middle font-sans shadow-(--kbd-cast) select-none\",\n        \"forced-colors:border forced-colors:border-[CanvasText]\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        data-slot=\"kbd-cap\"\n        className={cn(\n          \"flex flex-1 items-center justify-center rounded-(--bevel-radius-xs) bg-(--kbd-cap) px-1 text-(length:--bevel-label) leading-none font-medium text-(--kbd-foreground) shadow-(--kbd-cap-light)\",\n          \"transition-transform duration-(--bevel-duration-small) ease-(--bevel-ease-spring) motion-reduce:transition-none\",\n          \"group-data-pressed/kbd:translate-y-(--kbd-travel) group-data-pressed/kbd:duration-(--bevel-duration-press) group-data-pressed/kbd:ease-(--bevel-ease-out)\",\n          \"[&_svg]:pointer-events-none [&_svg]:size-3 [&_svg]:shrink-0\"\n        )}\n      >\n        {symbol ? (\n          <>\n            <span aria-hidden>{children}</span>\n            <span className=\"sr-only\">{symbol.name}</span>\n          </>\n        ) : (\n          children\n        )}\n      </span>\n    </kbd>\n  )\n}\n\n/** Caps pressed together, such as ⌘ K. Each cap listens for its own key. */\nfunction KbdGroup({ className, ...props }: React.ComponentProps<\"kbd\">) {\n  return (\n    <kbd\n      data-slot=\"kbd-group\"\n      className={cn(\"inline-flex items-center gap-1 align-middle font-sans\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Kbd, KbdGroup }\nexport type { KbdProps }\n",
      "type": "registry:ui"
    }
  ],
  "css": {
    ":root": {
      "--kbd-cap": "oklch(0.985 0.003 250)",
      "--kbd-side": "oklch(0.82 0.008 250)",
      "--kbd-foreground": "oklch(0.4 0.012 250)",
      "--kbd-travel": "2px"
    },
    ".dark": {
      "--kbd-cap": "oklch(0.34 0.008 250)",
      "--kbd-side": "oklch(0.21 0.008 250)",
      "--kbd-foreground": "oklch(0.88 0.01 250)"
    },
    "[data-slot=\"kbd\"]": {
      "--kbd-cap-light": "inset 0 1px 0 var(--bevel-highlight-strong), inset 0 0 0 1px oklch(0 0 0 / 0.05)",
      "--kbd-cast": "0 0 0 1px oklch(0 0 0 / 0.06), 0 1px 1px var(--bevel-cast)"
    }
  },
  "categories": [
    "essentials"
  ],
  "type": "registry:ui"
}