{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "kbd",
  "type": "registry:ui",
  "title": "Kbd",
  "description": "Inline keyboard-key badge — renders a native <kbd> for a single key, or nests one <kbd> per key inside an outer one for a shortcut like Ctrl + Shift + R. Zero JavaScript.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/kbd.tsx",
      "type": "registry:ui",
      "target": "components/ui/kbd.tsx",
      "content": "/** @jsxImportSource hono/jsx */\nimport type { Child, PropsWithChildren } from \"hono/jsx\"\nimport { cn, type ClassValue } from \"@/registry/lib/cn\"\n\n// Kbd — shadcn-htmx, htmx v4 + Tailwind v4.\n//\n// Native element (source of truth):\n//   repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md\n//     - <kbd> denotes textual user input from a keyboard / voice / other\n//       text-entry device. Implicit ARIA role: \"no corresponding role\"\n//       (it's phrasing content, announced as plain text). No interaction,\n//       so this component ships ZERO JavaScript.\n//     - MDN's \"Representing keystrokes within an input\" pattern nests a\n//       <kbd> per key inside an outer <kbd> that represents the whole\n//       shortcut. We follow that: <KbdGroup> is the outer <kbd>, each\n//       <Kbd> is an inner key. A lone <Kbd> (no group) is still a valid\n//       single-key <kbd>.\n//\n// Visual styling translated from shadcn/ui's Kbd (kept on-brand with our\n// theme tokens, not copied verbatim):\n//   repos/shadcn-ui/apps/v4/registry/new-york-v4/ui/kbd.tsx\n//\n// We use ONLY existing theme tokens (bg-muted / text-muted-foreground /\n// border). select-none + pointer-events-none keep the key glyphs from being\n// selected or clicked — they are a label, not a control.\n\nconst kbdBase =\n  \"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 \" +\n  \"rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground \" +\n  \"[&_svg:not([class*='size-'])]:size-3\"\n\n// The outer <kbd> that wraps a sequence of keys. MDN allows nesting <kbd>\n// inside <kbd>; the group carries no background of its own so the inner\n// keys read as discrete caps with separators (the \"+\" text) between them.\nconst kbdGroupBase = \"inline-flex w-fit items-center gap-1 align-middle\"\n\nexport function kbdClasses(opts?: { class?: ClassValue }): string {\n  return cn(kbdBase, opts?.class)\n}\n\nexport function kbdGroupClasses(opts?: { class?: ClassValue }): string {\n  return cn(kbdGroupBase, opts?.class)\n}\n\ntype KbdProps = PropsWithChildren<{\n  class?: ClassValue\n  id?: string\n  // Accessible name override — useful for symbol-only keys (e.g. content\n  // \"⌘\" with ariaLabel=\"Command\"). See aria-label on MDN.\n  ariaLabel?: string\n  title?: string\n  // htmx attrs / data-* / aria-* flow through via {...rest}.\n}>\n\n// A single key cap. Renders one <kbd>. Use inside <KbdGroup> for shortcuts.\nexport function Kbd(props: KbdProps) {\n  const { children, class: className, id, ariaLabel, title, ...rest } = props\n  return (\n    <kbd\n      id={id}\n      data-slot=\"kbd\"\n      class={kbdClasses({ class: className })}\n      aria-label={ariaLabel}\n      title={title}\n      {...rest}\n    >\n      {children}\n    </kbd>\n  )\n}\n\ntype KbdGroupProps = PropsWithChildren<{\n  class?: ClassValue\n  id?: string\n  // Convenience: render a shortcut from a list of key labels, joined by a\n  // visible separator. Omit `keys` and pass children to compose manually.\n  keys?: Child[]\n  // Separator rendered between keys (default \"+\"). Set \"\" for none.\n  separator?: Child\n  // Accessible name for the whole shortcut, e.g. \"Control Shift R\". The\n  // outer <kbd> is the labelled unit; per-key caps inherit no extra role.\n  ariaLabel?: string\n}>\n\n// The outer <kbd> wrapping a key sequence (MDN nested-kbd pattern). When\n// `keys` is supplied it renders one <Kbd> per entry with a separator text\n// node between them; otherwise it renders `children` verbatim so callers\n// can mix keys, \"+\" text, and icons by hand.\nexport function KbdGroup(props: KbdGroupProps) {\n  const {\n    children,\n    class: className,\n    id,\n    keys,\n    separator = \"+\",\n    ariaLabel,\n    ...rest\n  } = props\n\n  let body: Child\n  if (keys && keys.length > 0) {\n    const parts: Child[] = []\n    keys.forEach((k, i) => {\n      if (i > 0 && separator !== \"\" && separator != null) {\n        parts.push(\n          <span aria-hidden=\"true\" class=\"text-muted-foreground/70\">\n            {separator}\n          </span>,\n        )\n      }\n      parts.push(<Kbd>{k}</Kbd>)\n    })\n    body = parts\n  } else {\n    body = children\n  }\n\n  return (\n    <kbd\n      id={id}\n      data-slot=\"kbd-group\"\n      class={kbdGroupClasses({ class: className })}\n      aria-label={ariaLabel}\n      {...rest}\n    >\n      {body}\n    </kbd>\n  )\n}\n"
    },
    {
      "path": "registry/jinja2/kbd.html",
      "type": "registry:file",
      "target": "templates/components/kbd.html",
      "content": "{# Kbd macros — shadcn-htmx, htmx v4 + Tailwind v4.\n   Mirrors registry/ui/kbd.tsx. Zero JavaScript — <kbd> is phrasing\n   content with no interaction.\n\n   Native element:\n     repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md\n     (nested <kbd> per key inside an outer <kbd> = MDN keystroke pattern)\n\n   Usage:\n     {% from \"components/kbd.html\" import kbd, kbd_group %}\n     {{ kbd(\"Esc\") }}\n     {{ kbd_group([\"Ctrl\", \"Shift\", \"R\"]) }}\n     {% call kbd_group() %}<kbd ...>⌘</kbd> + <kbd ...>K</kbd>{% endcall %} #}\n\n{%- set kbd_base -%}\npointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3\n{%- endset -%}\n\n{%- set kbd_group_base -%}\ninline-flex w-fit items-center gap-1 align-middle\n{%- endset -%}\n\n{# A single key cap → one <kbd>. #}\n{% macro kbd(\n    text,\n    id=none,\n    aria_label=none,\n    title=none,\n    extra_class=\"\",\n    **attrs\n) %}\n<kbd\n  {%- if id %} id=\"{{ id }}\"{% endif %}\n  data-slot=\"kbd\"\n  class=\"{{ kbd_base }} {{ extra_class }}\"\n  {%- if aria_label %} aria-label=\"{{ aria_label }}\"{% endif %}\n  {%- if title %} title=\"{{ title }}\"{% endif %}\n  {%- for k, v in attrs.items() %} {{ k|replace('_', '-') }}=\"{{ v }}\"{% endfor -%}\n>{{ text }}</kbd>\n{%- endmacro %}\n\n{# The outer <kbd> wrapping a shortcut. Pass `keys` (a list) to render a\n   key cap per entry joined by `separator`, or use {% call %} with a body. #}\n{% macro kbd_group(\n    keys=none,\n    separator=\"+\",\n    id=none,\n    aria_label=none,\n    extra_class=\"\",\n    **attrs\n) %}\n<kbd\n  {%- if id %} id=\"{{ id }}\"{% endif %}\n  data-slot=\"kbd-group\"\n  class=\"{{ kbd_group_base }} {{ extra_class }}\"\n  {%- if aria_label %} aria-label=\"{{ aria_label }}\"{% endif %}\n  {%- for k, v in attrs.items() %} {{ k|replace('_', '-') }}=\"{{ v }}\"{% endfor -%}\n>\n  {%- if keys -%}\n    {%- for key in keys -%}\n      {%- if not loop.first and separator -%}<span aria-hidden=\"true\" class=\"text-muted-foreground/70\">{{ separator }}</span>{%- endif -%}\n      <kbd data-slot=\"kbd\" class=\"{{ kbd_base }}\">{{ key }}</kbd>\n    {%- endfor -%}\n  {%- else -%}\n    {{ caller() }}\n  {%- endif -%}\n</kbd>\n{%- endmacro %}\n"
    },
    {
      "path": "registry/go-templates/kbd.tmpl",
      "type": "registry:file",
      "target": "components/kbd.tmpl",
      "content": "{{/*\n  Kbd templates — shadcn-htmx, htmx v4 + Tailwind v4.\n  Mirrors registry/ui/kbd.tsx. Zero JavaScript — <kbd> is phrasing content.\n\n  Native element:\n    repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md\n    (nested <kbd> per key inside an outer <kbd> = MDN keystroke pattern)\n\n  Usage:\n\n      type KbdArgs struct {\n          Text             string\n          ID, AriaLabel    string\n          Title            string\n          Attrs            map[string]string\n      }\n      type KbdGroupArgs struct {\n          Keys             []string // one cap per entry\n          Separator        string   // default \"+\"\n          ID, AriaLabel    string\n          Body             template.HTML // used when Keys is empty\n          Attrs            map[string]string\n      }\n\n      {{template \"kbd\" (dict \"Text\" \"Esc\")}}\n      {{template \"kbd-group\" (dict \"Keys\" (list \"Ctrl\" \"Shift\" \"R\"))}}\n*/}}\n\n{{define \"kbd\"}}\n{{- $base := \"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3\" -}}\n<kbd\n  {{- if .ID}} id=\"{{.ID}}\"{{end}}\n  data-slot=\"kbd\"\n  class=\"{{$base}}\"\n  {{- if .AriaLabel}} aria-label=\"{{.AriaLabel}}\"{{end}}\n  {{- if .Title}} title=\"{{.Title}}\"{{end}}\n  {{- range $k, $v := .Attrs}} {{$k}}=\"{{$v}}\"{{end -}}\n>{{.Text}}</kbd>\n{{end}}\n\n{{define \"kbd-group\"}}\n{{- $base := \"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3\" -}}\n{{- $groupBase := \"inline-flex w-fit items-center gap-1 align-middle\" -}}\n{{- $sep := or .Separator \"+\" -}}\n<kbd\n  {{- if .ID}} id=\"{{.ID}}\"{{end}}\n  data-slot=\"kbd-group\"\n  class=\"{{$groupBase}}\"\n  {{- if .AriaLabel}} aria-label=\"{{.AriaLabel}}\"{{end}}\n  {{- range $k, $v := .Attrs}} {{$k}}=\"{{$v}}\"{{end -}}\n>\n  {{- if .Keys -}}\n    {{- range $i, $key := .Keys -}}\n      {{- if and (gt $i 0) $sep -}}<span aria-hidden=\"true\" class=\"text-muted-foreground/70\">{{$sep}}</span>{{- end -}}\n      <kbd data-slot=\"kbd\" class=\"{{$base}}\">{{$key}}</kbd>\n    {{- end -}}\n  {{- else -}}\n    {{- htmlSafe .Body -}}\n  {{- end -}}\n</kbd>\n{{end}}\n"
    },
    {
      "path": "registry/phoenix/kbd.ex",
      "type": "registry:file",
      "target": "lib/my_app_web/components/kbd.ex",
      "content": "defmodule ShadcnHtmx.Components.Kbd do\n  @moduledoc \"\"\"\n  Kbd — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.\n\n  Mirrors registry/ui/kbd.tsx. Zero JavaScript — `<kbd>` is phrasing\n  content with no interaction.\n\n  Native element:\n    repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md\n    (nested `<kbd>` per key inside an outer `<kbd>` = MDN keystroke pattern)\n\n  `kbd/1` renders one key cap. `kbd_group/1` renders the outer `<kbd>`\n  wrapping a shortcut — pass `keys` for an auto-joined sequence, or use the\n  default slot to compose the body by hand.\n  \"\"\"\n\n  use Phoenix.Component\n\n  @base \"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 \" <>\n          \"rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground \" <>\n          \"[&_svg:not([class*='size-'])]:size-3\"\n\n  @group_base \"inline-flex w-fit items-center gap-1 align-middle\"\n\n  attr :id, :string, default: nil\n  attr :aria_label, :string, default: nil\n  attr :title, :string, default: nil\n  attr :class, :string, default: nil\n  attr :rest, :global\n\n  slot :inner_block, required: true\n\n  def kbd(assigns) do\n    assigns = assign(assigns, :base_class, @base)\n\n    ~H\"\"\"\n    <kbd\n      id={@id}\n      data-slot=\"kbd\"\n      class={[@base_class, @class]}\n      aria-label={@aria_label}\n      title={@title}\n      {@rest}\n    >\n      {render_slot(@inner_block)}\n    </kbd>\n    \"\"\"\n  end\n\n  attr :keys, :list, default: nil\n  attr :separator, :string, default: \"+\"\n  attr :id, :string, default: nil\n  attr :aria_label, :string, default: nil\n  attr :class, :string, default: nil\n  attr :rest, :global\n\n  slot :inner_block\n\n  def kbd_group(assigns) do\n    assigns =\n      assigns\n      |> assign(:base_class, @base)\n      |> assign(:group_class, @group_base)\n\n    ~H\"\"\"\n    <kbd\n      id={@id}\n      data-slot=\"kbd-group\"\n      class={[@group_class, @class]}\n      aria-label={@aria_label}\n      {@rest}\n    >\n      <%= if @keys && @keys != [] do %>\n        <%= for {key, i} <- Enum.with_index(@keys) do %>\n          <span :if={i > 0 && @separator != \"\"} aria-hidden=\"true\" class=\"text-muted-foreground/70\">{@separator}</span>\n          <kbd data-slot=\"kbd\" class={@base_class}>{key}</kbd>\n        <% end %>\n      <% else %>\n        {render_slot(@inner_block)}\n      <% end %>\n    </kbd>\n    \"\"\"\n  end\nend\n"
    },
    {
      "path": "registry/html/kbd.html",
      "type": "registry:file",
      "target": "snippets/kbd.html",
      "content": "<!--\n  shadcn-htmx — raw <kbd> snippets. Mirrors registry/ui/kbd.tsx.\n  Zero JavaScript — <kbd> is phrasing content with no interaction.\n\n  Native element:\n    repos/mdn/files/en-us/web/html/reference/elements/kbd/index.md\n    (nested <kbd> per key inside an outer <kbd> = MDN keystroke pattern)\n\n  KEY CAP base (data-slot=\"kbd\"):\n    pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none\n    items-center justify-center gap-1 rounded-sm border bg-muted px-1\n    font-sans text-xs font-medium text-muted-foreground\n    [&_svg:not([class*='size-'])]:size-3\n\n  GROUP base (data-slot=\"kbd-group\", the outer wrapping <kbd>):\n    inline-flex w-fit items-center gap-1 align-middle\n  Relies only on theme tokens; no script.\n-->\n\n<!-- Single key -->\n<kbd data-slot=\"kbd\"\n  class=\"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3\">\n  Esc\n</kbd>\n\n<!-- Shortcut: outer <kbd> groups inner key caps, \"+\" between them -->\n<kbd data-slot=\"kbd-group\" class=\"inline-flex w-fit items-center gap-1 align-middle\">\n  <kbd data-slot=\"kbd\"\n    class=\"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3\">Ctrl</kbd>\n  <span aria-hidden=\"true\" class=\"text-muted-foreground/70\">+</span>\n  <kbd data-slot=\"kbd\"\n    class=\"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3\">Shift</kbd>\n  <span aria-hidden=\"true\" class=\"text-muted-foreground/70\">+</span>\n  <kbd data-slot=\"kbd\"\n    class=\"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3\">R</kbd>\n</kbd>\n\n<!-- Symbol key with an accessible name (aria-label) for screen readers -->\n<kbd data-slot=\"kbd\" aria-label=\"Command\"\n  class=\"pointer-events-none inline-flex h-5 w-fit min-w-5 shrink-0 select-none items-center justify-center gap-1 rounded-sm border bg-muted px-1 font-sans text-xs font-medium text-muted-foreground [&_svg:not([class*='size-'])]:size-3\">\n  ⌘\n</kbd>\n"
    }
  ]
}
