{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "accordion",
  "type": "registry:ui",
  "title": "Accordion",
  "description": "Collapsible stacked sections. Native <details>/<summary> + new HTML name attribute for zero-JS exclusive accordion. APG keyboard nav in site.js.",
  "files": [
    {
      "path": "registry/ui/accordion.tsx",
      "type": "registry:ui",
      "target": "components/ui/accordion.tsx",
      "content": "/** @jsxImportSource hono/jsx */\nimport type { PropsWithChildren } from \"hono/jsx\"\nimport { cn, type ClassValue } from \"@/registry/lib/cn\"\n\n// Accordion — shadcn-htmx, htmx v4 + Tailwind v4.\n//\n// shadcn upstream uses Radix Accordion. We use the native HTML\n//   <details><summary>...</summary>...</details>\n// so the platform gives us:\n//   - Click / Space / Enter toggles open (browser default).\n//   - aria-expanded mirrors the open attribute (browser-set on <summary>).\n//   - <summary> is implicitly role=\"button\" with accessible name from text.\n//\n// Native `<details name=\"...\">` attribute (HTML Living Standard) makes the\n// group exclusive — opening one item closes the others, no JS required.\n// We use that for `type=\"single\"`. For `type=\"multiple\"` (the default) we\n// omit the name attribute and every item can be expanded independently.\n//\n// Public/site.js adds the APG keyboard contract on top:\n//   - Down/Up Arrow: move focus between summaries in the same accordion.\n//   - Home / End: focus first / last summary.\n//\n// Refs:\n//   repos/mdn/files/en-us/web/html/reference/elements/details/index.md\n//   repos/mdn/files/en-us/web/html/reference/elements/summary/index.md\n//   repos/aria-practices/content/patterns/accordion/\n\nexport type AccordionType = \"single\" | \"multiple\"\n\ntype AccordionProps = PropsWithChildren<{\n  // Required so the exclusive-accordion `name` attribute can scope items.\n  id: string\n  type?: AccordionType\n  class?: ClassValue\n}>\n\nexport function Accordion(props: AccordionProps) {\n  const { id, type = \"multiple\", class: className, children } = props\n  return (\n    <div\n      id={id}\n      data-slot=\"accordion\"\n      data-accordion\n      data-type={type}\n      data-group-name={type === \"single\" ? id : undefined}\n      class={cn(\"w-full\", className)}\n    >\n      {children}\n    </div>\n  )\n}\n\ntype AccordionItemProps = PropsWithChildren<{\n  // Distinct value per item. Emitted as the `data-value` attribute so each\n  // item is individually identifiable; the boot script does not derive any\n  // aria-controls wiring from it. (Native <details>/<summary> is a disclosure\n  // widget where aria-controls is optional per the WAI-ARIA Disclosure\n  // pattern — repos/aria-practices/content/patterns/disclosure/.)\n  value: string\n  // Pre-open this item on initial render.\n  open?: boolean\n  disabled?: boolean\n  class?: ClassValue\n}>\n\nexport function AccordionItem(props: AccordionItemProps) {\n  const { value, open, disabled, class: className, children, ...rest } = props\n  return (\n    <details\n      data-slot=\"accordion-item\"\n      data-value={value}\n      data-disabled={disabled ? \"true\" : undefined}\n      // The boot script in registry/ui/accordion's Accordion wrapper assigns\n      // the `name` attribute at render time for type=\"single\" so it groups\n      // exclusively. (We can't compute it here without context.)\n      open={open}\n      class={cn(\n        \"border-b last:border-b-0\",\n        disabled && \"pointer-events-none opacity-50\",\n        className,\n      )}\n      // Forward hx-* / global attributes so the native `toggle` event (fired\n      // by <details> just after open/close) can drive zero-JS lazy loading,\n      // e.g. hx-trigger=\"toggle once\" hx-get=...\n      // repos/mdn/files/en-us/web/api/htmlelement/toggle_event/index.md\n      // repos/htmx hx-trigger: accepts any DOM event.\n      {...rest}\n    >\n      {children}\n    </details>\n  )\n}\n\ntype AccordionTriggerProps = PropsWithChildren<{ class?: ClassValue }>\n\nexport function AccordionTrigger(props: AccordionTriggerProps) {\n  const { class: className, children, ...rest } = props\n  return (\n    <summary\n      data-slot=\"accordion-trigger\"\n      class={cn(\n        \"flex cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none select-none marker:hidden \" +\n          \"hover:underline \" +\n          \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 \" +\n          \"list-none [&::-webkit-details-marker]:hidden \" +\n          // Rotate the chevron when the parent <details> is open.\n          \"[details[open]>&_[data-slot=accordion-chevron]]:rotate-180\",\n        className,\n      )}\n      // Forward hx-* / global attributes onto the <summary> control.\n      {...rest}\n    >\n      {children}\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 24 24\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        stroke-width=\"2\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"\n        data-slot=\"accordion-chevron\"\n        class=\"pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200\"\n        aria-hidden=\"true\"\n      >\n        <polyline points=\"6 9 12 15 18 9\" />\n      </svg>\n    </summary>\n  )\n}\n\ntype AccordionContentProps = PropsWithChildren<{ class?: ClassValue }>\n\nexport function AccordionContent(props: AccordionContentProps) {\n  const { class: className, children, ...rest } = props\n  return (\n    <div\n      data-slot=\"accordion-content\"\n      class={cn(\"overflow-hidden pt-0 pb-4 text-sm\", className)}\n      // Forward hx-* / global attributes so a panel can lazy-load on first\n      // open: hx-trigger=\"toggle once\" hx-get=... on the enclosing <details>,\n      // or hx-* directly here. repos/htmx hx-trigger accepts any DOM event.\n      {...rest}\n    >\n      {children}\n    </div>\n  )\n}\n"
    },
    {
      "path": "registry/jinja2/accordion.html",
      "type": "registry:file",
      "target": "templates/components/accordion.html",
      "content": "{# Accordion macros — shadcn-htmx, htmx v4 + Tailwind v4.\n   Mirrors registry/ui/accordion.tsx. Native <details>/<summary>; the boot\n   script in public/site.js wires the `name` attribute for type=\"single\"\n   (browser-native exclusive accordion) and the APG keyboard contract.\n\n   Usage:\n     {% from \"components/accordion.html\" import accordion_open, accordion_close,\n        accordion_item_open, accordion_item_close,\n        accordion_trigger, accordion_content_open, accordion_content_close %}\n\n     {{ accordion_open(id=\"faq\", type=\"single\") }}\n       {{ accordion_item_open(value=\"q1\", open=true) }}\n         {{ accordion_trigger(\"What's htmx?\") }}\n         {{ accordion_content_open() }}\n           Hypermedia-driven HTML extensions.\n         {{ accordion_content_close() }}\n       {{ accordion_item_close() }}\n     {{ accordion_close() }} #}\n\n{% macro accordion_open(id, type=\"multiple\", extra_class=\"\") -%}\n<div id=\"{{ id }}\"\n     data-slot=\"accordion\" data-accordion data-type=\"{{ type }}\"\n     {%- if type == \"single\" %} data-group-name=\"{{ id }}\"{% endif %}\n     class=\"w-full {{ extra_class }}\">\n{%- endmacro %}\n\n{% macro accordion_close() %}</div>{% endmacro %}\n\n{# attrs forwards hx-* / global attributes so the native <details> `toggle`\n   event can drive zero-JS lazy loading (hx-trigger=\"toggle once\" hx-get=...);\n   toggle event fires just after open/close; hx-trigger accepts any DOM event. #}\n{% macro accordion_item_open(value, open=false, disabled=false, extra_class=\"\", attrs={}) -%}\n<details data-slot=\"accordion-item\" data-value=\"{{ value }}\"\n         {%- if disabled %} data-disabled=\"true\"{% endif %}\n         {%- if open %} open{% endif %}\n         class=\"border-b last:border-b-0 {% if disabled %}pointer-events-none opacity-50{% endif %} {{ extra_class }}\"\n         {%- for k, v in attrs.items() %} {{ k|replace('_','-') }}=\"{{ v }}\"{% endfor %}>\n{%- endmacro %}\n\n{% macro accordion_item_close() %}</details>{% endmacro %}\n\n{% macro accordion_trigger(text, extra_class=\"\", attrs={}) -%}\n<summary data-slot=\"accordion-trigger\"\n  class=\"flex cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none select-none marker:hidden hover:underline focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 list-none [&::-webkit-details-marker]:hidden [details[open]>&_[data-slot=accordion-chevron]]:rotate-180 {{ extra_class }}\"\n  {%- for k, v in attrs.items() %} {{ k|replace('_','-') }}=\"{{ v }}\"{% endfor %}>\n  {{ text }}\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n       data-slot=\"accordion-chevron\"\n       class=\"pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200\" aria-hidden=\"true\">\n    <polyline points=\"6 9 12 15 18 9\" />\n  </svg>\n</summary>\n{%- endmacro %}\n\n{% macro accordion_content_open(extra_class=\"\", attrs={}) -%}\n<div data-slot=\"accordion-content\" class=\"overflow-hidden pt-0 pb-4 text-sm {{ extra_class }}\"\n     {%- for k, v in attrs.items() %} {{ k|replace('_','-') }}=\"{{ v }}\"{% endfor %}>\n{%- endmacro %}\n\n{% macro accordion_content_close() %}</div>{% endmacro %}\n"
    },
    {
      "path": "registry/go-templates/accordion.tmpl",
      "type": "registry:file",
      "target": "components/accordion.tmpl",
      "content": "{{/*\n  Accordion templates — shadcn-htmx, htmx v4 + Tailwind v4.\n\n  Three named templates compose:\n    - \"accordion\"          — wrapper open/close (call with Body)\n    - \"accordion_item\"     — single <details> with summary + content\n    - \"accordion_trigger\"  — just the <summary> (when composing manually)\n*/}}\n\n{{define \"accordion\"}}\n{{- $type := or .Type \"multiple\" -}}\n<div id=\"{{.ID}}\"\n     data-slot=\"accordion\" data-accordion data-type=\"{{$type}}\"\n     {{if eq $type \"single\"}}data-group-name=\"{{.ID}}\"{{end}}\n     class=\"w-full\">\n  {{.Body}}\n</div>\n{{end}}\n\n{{/* .Attrs (htmlSafe) forwards hx-* / global attributes onto the <details>\n     so the native `toggle` event it fires can drive zero-JS lazy loading:\n     hx-trigger=\"toggle once\" hx-get=...  (toggle event: HTMLElement;\n     hx-trigger accepts any DOM event). */}}\n{{define \"accordion_item\"}}\n<details data-slot=\"accordion-item\" data-value=\"{{.Value}}\"\n         {{if .Disabled}}data-disabled=\"true\"{{end}}\n         {{if .Open}}open{{end}}\n         class=\"border-b last:border-b-0\"{{with .Attrs}} {{.}}{{end}}>\n  {{template \"accordion_trigger\" (dict \"Text\" .Title)}}\n  <div data-slot=\"accordion-content\" class=\"overflow-hidden pt-0 pb-4 text-sm\">{{.Body}}</div>\n</details>\n{{end}}\n\n{{define \"accordion_trigger\"}}\n<summary data-slot=\"accordion-trigger\"\n  class=\"flex cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none select-none marker:hidden hover:underline focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 list-none [&::-webkit-details-marker]:hidden [details[open]>&_[data-slot=accordion-chevron]]:rotate-180\"{{with .Attrs}} {{.}}{{end}}>\n  {{.Text}}\n  <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n       data-slot=\"accordion-chevron\"\n       class=\"pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200\" aria-hidden=\"true\">\n    <polyline points=\"6 9 12 15 18 9\" />\n  </svg>\n</summary>\n{{end}}\n"
    },
    {
      "path": "registry/phoenix/accordion.ex",
      "type": "registry:file",
      "target": "lib/my_app_web/components/accordion.ex",
      "content": "defmodule ShadcnHtmx.Components.Accordion do\n  @moduledoc \"\"\"\n  Accordion — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.\n\n  Native `<details>` + `<summary>`. Single-expand mode (`type=\"single\"`)\n  uses the new HTML `name` attribute which makes the group exclusive\n  with zero JS. Multi-expand mode just omits the name.\n\n  Keyboard nav (arrows, Home/End) lives in public/site.js.\n\n  ## Examples\n\n      <.accordion id=\"faq\" type=\"single\">\n        <.accordion_item value=\"q1\" open>\n          <.accordion_trigger>What's htmx?</.accordion_trigger>\n          <.accordion_content>Hypermedia-driven HTML extensions.</.accordion_content>\n        </.accordion_item>\n      </.accordion>\n  \"\"\"\n\n  use Phoenix.Component\n\n  attr :id, :string, required: true\n  attr :type, :string, default: \"multiple\", values: ~w(single multiple)\n  attr :class, :string, default: nil\n  slot :inner_block, required: true\n\n  def accordion(assigns) do\n    ~H\"\"\"\n    <div\n      id={@id}\n      data-slot=\"accordion\"\n      data-accordion\n      data-type={@type}\n      data-group-name={if @type == \"single\", do: @id}\n      class={[\"w-full\", @class]}\n    >\n      {render_slot(@inner_block)}\n    </div>\n    \"\"\"\n  end\n\n  attr :value, :string, required: true\n  attr :open, :boolean, default: false\n  attr :disabled, :boolean, default: false\n  attr :class, :string, default: nil\n  # Forward hx-* / global attributes so the native `toggle` event <details>\n  # fires can drive zero-JS lazy loading: hx-trigger=\"toggle once\" hx-get=...\n  # (toggle event: HTMLElement; hx-trigger accepts any DOM event).\n  attr :rest, :global\n  slot :inner_block, required: true\n\n  def accordion_item(assigns) do\n    ~H\"\"\"\n    <details\n      data-slot=\"accordion-item\"\n      data-value={@value}\n      data-disabled={@disabled && \"true\"}\n      open={@open}\n      class={[\n        \"border-b last:border-b-0\",\n        @disabled && \"pointer-events-none opacity-50\",\n        @class\n      ]}\n      {@rest}\n    >\n      {render_slot(@inner_block)}\n    </details>\n    \"\"\"\n  end\n\n  attr :class, :string, default: nil\n  attr :rest, :global\n  slot :inner_block, required: true\n\n  def accordion_trigger(assigns) do\n    ~H\"\"\"\n    <summary\n      data-slot=\"accordion-trigger\"\n      class={[\n        \"flex cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium\",\n        \"transition-all outline-none select-none marker:hidden hover:underline\",\n        \"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n        \"list-none [&::-webkit-details-marker]:hidden\",\n        \"[details[open]>&_[data-slot=accordion-chevron]]:rotate-180\",\n        @class\n      ]}\n      {@rest}\n    >\n      {render_slot(@inner_block)}\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\"\n        stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n        data-slot=\"accordion-chevron\"\n        class=\"pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200\"\n        aria-hidden=\"true\"\n      >\n        <polyline points=\"6 9 12 15 18 9\" />\n      </svg>\n    </summary>\n    \"\"\"\n  end\n\n  attr :class, :string, default: nil\n  attr :rest, :global\n  slot :inner_block, required: true\n\n  def accordion_content(assigns) do\n    ~H\"\"\"\n    <div data-slot=\"accordion-content\" class={[\"overflow-hidden pt-0 pb-4 text-sm\", @class]} {@rest}>\n      {render_slot(@inner_block)}\n    </div>\n    \"\"\"\n  end\nend\n"
    },
    {
      "path": "registry/html/accordion.html",
      "type": "registry:file",
      "target": "snippets/accordion.html",
      "content": "<!--\n  shadcn-htmx — raw HTML accordion snippet.\n\n  Native <details> + <summary>. For exclusive (single-expand) accordions\n  use the new HTML `name` attribute that groups items — browser handles\n  the \"close the others\" behaviour with zero JS.\n\n  For arrow-key navigation between summaries and Home/End shortcuts copy\n  the inline JS at the bottom.\n\n  Zero-JS lazy loading: <details> fires a native `toggle` event just after it\n  opens/closes, and htmx hx-trigger accepts any DOM event — so adding\n  hx-trigger=\"toggle once\" hx-get=\"/panel\" to a <details> (or its content\n  <div>) fetches the panel only on first open. Add hx-* / aria-* inline.\n-->\n\n<!-- Single-expand: items share data-group-name=name attribute -->\n<div id=\"faq\" data-slot=\"accordion\" data-accordion data-type=\"single\" data-group-name=\"faq\" class=\"w-full\">\n\n  <details name=\"faq\" data-slot=\"accordion-item\" data-value=\"q1\" open class=\"border-b last:border-b-0\">\n    <summary data-slot=\"accordion-trigger\"\n      class=\"flex cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none select-none marker:hidden hover:underline focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 list-none [&::-webkit-details-marker]:hidden [details[open]>&_[data-slot=accordion-chevron]]:rotate-180\">\n      What's htmx?\n      <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n           data-slot=\"accordion-chevron\" class=\"pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200\" aria-hidden=\"true\">\n        <polyline points=\"6 9 12 15 18 9\" />\n      </svg>\n    </summary>\n    <div data-slot=\"accordion-content\" class=\"overflow-hidden pt-0 pb-4 text-sm\">\n      Hypermedia-driven HTML extensions. AJAX, WebSockets, server-sent events with attributes.\n    </div>\n  </details>\n\n  <details name=\"faq\" data-slot=\"accordion-item\" data-value=\"q2\" class=\"border-b last:border-b-0\">\n    <summary data-slot=\"accordion-trigger\" class=\"flex cursor-pointer items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none select-none marker:hidden hover:underline focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 list-none [&::-webkit-details-marker]:hidden [details[open]>&_[data-slot=accordion-chevron]]:rotate-180\">\n      Why pair it with Tailwind v4?\n      <svg data-slot=\"accordion-chevron\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200\" aria-hidden=\"true\">\n        <polyline points=\"6 9 12 15 18 9\" />\n      </svg>\n    </summary>\n    <div data-slot=\"accordion-content\" class=\"overflow-hidden pt-0 pb-4 text-sm\">\n      Utility-first CSS keeps the markup self-explanatory and the bundle small.\n    </div>\n  </details>\n</div>\n\n<!-- Arrow-key navigation across summaries -->\n<script>\n  document.addEventListener('keydown', function (e) {\n    var t = e.target.closest && e.target.closest('[data-slot=\"accordion-trigger\"]')\n    if (!t) return\n    var group = t.closest('[data-accordion]'); if (!group) return\n    var keys = { ArrowDown: 1, ArrowUp: -1, Home: 'h', End: 'e' }\n    if (!(e.key in keys)) return\n    e.preventDefault()\n    var list = [].slice.call(group.querySelectorAll('details:not([data-disabled=\"true\"]) > [data-slot=\"accordion-trigger\"]'))\n    var i = list.indexOf(t), d = keys[e.key], to\n    if (d === 1)  to = list[(i + 1) % list.length]\n    if (d === -1) to = list[(i - 1 + list.length) % list.length]\n    if (d === 'h') to = list[0]\n    if (d === 'e') to = list[list.length - 1]\n    if (to) to.focus()\n  })\n</script>\n"
    }
  ]
}
