{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "kbd",
  "title": "Kbd",
  "description": "Keyboard keys as hairline caps, physical keys, tight chips or plain text, with chords, a platform-aware modifier and a cap that dips on the real key.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/kbd.tsx",
      "content": "import type * as React from \"react\"\nimport { Children, Fragment, createContext, useContext, useEffect, useMemo, useState, useSyncExternalStore } from \"react\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype KbdVariant = \"plain\" | \"raised\" | \"flat\" | \"ghost\"\ntype KbdEffect = \"press\"\ntype KbdPlatform = \"mac\" | \"other\"\n\nconst BASE_CLASS = [\n  \"inline-flex items-center justify-center gap-1 font-sans font-medium whitespace-nowrap tabular-nums select-none\",\n  \"transition-[translate,background-color,color,border-width] duration-(--duration-fast) ease-(--ease-mise)\",\n  \"data-[pressed=true]:duration-0 motion-reduce:transition-none\",\n]\n\nconst VARIANT_CLASS: Record<KbdVariant, string> = {\n  plain: \"h-6 min-w-6 rounded-sm border bg-background px-1 text-xs text-muted-foreground data-[pressed=true]:bg-accent data-[pressed=true]:text-foreground\",\n  // The thick bottom edge is the side of the cap. Pressing thins it and drops the face by the same pixel.\n  raised: [\n    \"h-8 min-w-8 rounded-md border border-b-2 bg-card px-2 text-[13px] text-foreground shadow-xs\",\n    \"data-[pressed=true]:translate-y-px data-[pressed=true]:border-b data-[pressed=true]:bg-accent data-[pressed=true]:shadow-none\",\n    \"motion-reduce:data-[pressed=true]:translate-y-0\",\n  ].join(\" \"),\n  flat: \"h-5 min-w-5 rounded-sm bg-muted px-1 text-xs text-foreground data-[pressed=true]:bg-accent\",\n  ghost: \"text-xs text-muted-foreground data-[pressed=true]:text-foreground\",\n}\n\nconst KEY_ALIASES: Readonly<Record<string, string>> = {\n  \"⌘\": \"meta\", cmd: \"meta\", command: \"meta\",\n  \"⌃\": \"control\", ctrl: \"control\",\n  \"⌥\": \"alt\", opt: \"alt\", option: \"alt\",\n  \"⇧\": \"shift\",\n  \"↵\": \"enter\", \"⏎\": \"enter\", return: \"enter\",\n  esc: \"escape\", \"⎋\": \"escape\",\n  \"⌫\": \"backspace\", del: \"delete\", \"⇥\": \"tab\",\n  space: \" \", spacebar: \" \",\n  \"↑\": \"arrowup\", \"↓\": \"arrowdown\", \"←\": \"arrowleft\", \"→\": \"arrowright\",\n}\n\nfunction normalizeKey(name: string) {\n  const lower = name.toLowerCase()\n  return KEY_ALIASES[lower] ?? lower\n}\n\n/** `event.key` against what the cap says, so \"Esc\" and \"⌘\" still match \"Escape\" and \"Meta\". */\nfunction keyMatches(eventKey: string, name: string) {\n  return normalizeKey(eventKey) === normalizeKey(name)\n}\n\nfunction isApplePlatform(platform: string) {\n  return /mac|iphone|ipad|ipod/i.test(platform)\n}\n\nfunction readPlatform() {\n  const { userAgentData, platform } = navigator as Navigator & { userAgentData?: { platform?: string } }\n  return userAgentData?.platform ?? platform ?? \"\"\n}\n\nconst subscribeNever = () => () => undefined\nconst detectApple = () => isApplePlatform(readPlatform())\n/** The server cannot know the platform; it renders Ctrl and the client swaps without a hydration error. */\nconst detectAppleOnServer = () => false\n\nfunction useKeyPressed(name: string | undefined) {\n  const [pressed, setPressed] = useState(false)\n  useEffect(() => {\n    if (name === undefined) return\n    const onKeyDown = (event: KeyboardEvent) => {\n      if (keyMatches(event.key, name)) setPressed(true)\n    }\n    const onKeyUp = (event: KeyboardEvent) => {\n      // macOS sends no keyup for any key while Command is held, so Command's own keyup has to release everything.\n      if (keyMatches(event.key, name) || event.key === \"Meta\") setPressed(false)\n    }\n    // Switching windows mid-press swallows the keyup too.\n    const release = () => setPressed(false)\n    window.addEventListener(\"keydown\", onKeyDown)\n    window.addEventListener(\"keyup\", onKeyUp)\n    window.addEventListener(\"blur\", release)\n    return () => {\n      window.removeEventListener(\"keydown\", onKeyDown)\n      window.removeEventListener(\"keyup\", onKeyUp)\n      window.removeEventListener(\"blur\", release)\n    }\n  }, [name])\n  return name !== undefined && pressed\n}\n\ninterface KbdGroupContextValue {\n  variant?: KbdVariant\n  effect?: KbdEffect\n}\n\nconst KbdGroupContext = createContext<KbdGroupContextValue>({})\n\ntype KbdProps = React.ComponentProps<\"kbd\"> & {\n  /** `plain` a hairline cap, `raised` a physical key, `flat` a filled chip for tight rows, `ghost` text only. */\n  variant?: KbdVariant\n  /** `press` dips the cap while the real key is held. */\n  effect?: KbdEffect\n  /** The key `press` listens for. Worked out from string children, so only needed around an icon. */\n  keyName?: string\n}\n\nfunction Kbd({ variant, effect, keyName, className, children, ...props }: KbdProps) {\n  const group = useContext(KbdGroupContext)\n  const resolvedVariant = variant ?? group.variant ?? \"plain\"\n  const resolvedEffect = effect ?? group.effect\n  const name = keyName ?? (typeof children === \"string\" ? children : undefined)\n  const pressed = useKeyPressed(resolvedEffect === \"press\" ? name : undefined)\n  return (\n    <kbd\n      data-slot=\"kbd\"\n      data-variant={resolvedVariant}\n      data-effect={resolvedEffect}\n      data-pressed={resolvedEffect === \"press\" ? pressed : undefined}\n      className={cn(BASE_CLASS, VARIANT_CLASS[resolvedVariant], className)}\n      {...props}\n    >\n      {children}\n    </kbd>\n  )\n}\n\ntype KbdGroupProps = React.ComponentProps<\"kbd\"> & {\n  variant?: KbdVariant\n  effect?: KbdEffect\n  /** Goes between the keys, such as \"+\" for a chord or \"then\" for a sequence. */\n  separator?: React.ReactNode\n}\n\n/** Nested kbd elements are how HTML spells a key combination. */\nfunction KbdGroup({ variant, effect, separator, className, children, ...props }: KbdGroupProps) {\n  const context = useMemo(() => ({ variant, effect }), [variant, effect])\n  const keys = Children.toArray(children)\n  return (\n    <KbdGroupContext.Provider value={context}>\n      <kbd data-slot=\"kbd-group\" className={cn(\"inline-flex items-center gap-1 font-sans\", className)} {...props}>\n        {keys.map((entry, index) => (\n          <Fragment key={index}>\n            {index > 0 && separator ? <span className=\"text-xs text-muted-foreground\">{separator}</span> : null}\n            {entry}\n          </Fragment>\n        ))}\n      </kbd>\n    </KbdGroupContext.Provider>\n  )\n}\n\ntype ModProps = Omit<KbdProps, \"children\" | \"keyName\"> & {\n  /** Forces one platform, for screenshots and docs. Detected from the browser otherwise. */\n  platform?: KbdPlatform\n}\n\n/** The primary modifier: Command on Apple devices, Control everywhere else. */\nfunction Mod({ platform, ...props }: ModProps) {\n  const detected = useSyncExternalStore(subscribeNever, detectApple, detectAppleOnServer)\n  const apple = platform ? platform === \"mac\" : detected\n  return (\n    <Kbd keyName={apple ? \"Meta\" : \"Control\"} {...props}>\n      <span aria-hidden=\"true\">{apple ? \"⌘\" : \"Ctrl\"}</span>\n      <span className=\"sr-only\">{apple ? \"Command\" : \"Control\"}</span>\n    </Kbd>\n  )\n}\n\nexport {\n  Kbd,\n  KbdGroup,\n  Mod,\n  isApplePlatform,\n  keyMatches,\n  type KbdProps,\n  type KbdGroupProps,\n  type ModProps,\n  type KbdVariant,\n  type KbdEffect,\n  type KbdPlatform,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "display"
  ],
  "type": "registry:ui"
}