{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "faq-accordion",
  "type": "registry:block",
  "title": "FAQ accordion",
  "description": "A FAQ section: questions and answers built on Base UI's Accordion. Each question is a button inside a heading, browser search finds text in closed answers, and you can add FAQPage structured data.",
  "categories": [
    "section"
  ],
  "dependencies": [
    "@base-ui-components/react@1.0.0-rc.0",
    "lucide-react@^1.47.0"
  ],
  "registryDependencies": [
    "https://facadeui.dev/r/container.json",
    "https://facadeui.dev/r/heading.json",
    "https://facadeui.dev/r/section.json",
    "https://facadeui.dev/r/section-header.json",
    "https://facadeui.dev/r/types.json",
    "https://facadeui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/sections/faq-accordion.tsx",
      "type": "registry:component",
      "target": "components/sections/faq-accordion.tsx",
      "content": "/**\n * FaqAccordion — questions and answers as a Base UI Accordion.\n *\n * a11y: Base UI supplies the `aria-expanded`, `aria-controls`, roving focus and\n * Home/End handling. What this file is responsible for is the part a library\n * cannot decide:\n *\n *  - Each question is a real `<button>` inside a heading, at a level derived\n *    from the section's own. Screen-reader users navigate an FAQ by heading, and\n *    a list of `<div>`s makes that impossible.\n *  - `hiddenUntilFound` is on by default, so closed answers stay in the DOM with\n *    `hidden=\"until-found\"`. Browser find-in-page can then reach a closed answer\n *    and open it — which is how people actually use a long FAQ.\n *  - The chevron is `aria-hidden`; expanded state is announced by\n *    `aria-expanded`, not by an icon's rotation.\n *\n * The open/close animation is plain CSS driven by Base UI's\n * `--accordion-panel-height`, so the static variant animates without any\n * JavaScript motion library, and the reduced-motion block in the token layer\n * switches it off.\n *\n * Set `schemaOrg` to emit FAQPage structured data. It only includes answers\n * given as plain strings, since markup cannot go in JSON-LD.\n *\n * Dependencies: @base-ui-components/react, lucide-react, react,\n * @/lib/types, @/lib/utils, @/components/ui/container,\n * @/components/ui/heading, @/components/ui/section, @/components/ui/section-header.\n */\n\nimport { Accordion } from \"@base-ui-components/react/accordion\"\nimport { ChevronDownIcon } from \"lucide-react\"\nimport type { ElementType, ReactNode } from \"react\"\n\nimport type { HeadingLevel, ListSlotProps, SectionBaseProps } from \"@/lib/types\"\nimport { cn, slugId } from \"@/lib/utils\"\nimport { Container } from \"@/components/ui/container\"\nimport { Heading } from \"@/components/ui/heading\"\nimport { Section } from \"@/components/ui/section\"\nimport { SectionHeader } from \"@/components/ui/section-header\"\n\nexport interface FaqItem {\n  question: string\n  /** Rich content is fine; only string answers reach the structured data. */\n  answer: ReactNode\n  /** Plain-text answer for `schemaOrg`, when `answer` contains markup. */\n  plainAnswer?: string\n  /** Stable value. Defaults to a slug of the question. */\n  value?: string\n}\n\nexport interface FaqAccordionProps extends SectionBaseProps, ListSlotProps {\n  items: FaqItem[]\n  title?: string\n  eyebrow?: string\n  description?: string\n  /** Questions that start open, by `value` (or question slug). */\n  defaultOpen?: string[]\n  /** Allow several answers open at once. Default `false`. */\n  multiple?: boolean\n  itemHeadingLevel?: HeadingLevel\n  /** Lets browser find-in-page reach closed answers. Default `true`. */\n  hiddenUntilFound?: boolean\n  align?: \"start\" | \"center\"\n  /** Adds FAQPage JSON-LD for the items with plain-text answers. */\n  schemaOrg?: boolean\n}\n\nconst valueOf = (item: FaqItem): string => item.value ?? slugId(item.question, \"faq\")\n\nexport function FaqAccordion({\n  items,\n  title,\n  eyebrow,\n  description,\n  defaultOpen = [],\n  multiple = false,\n  itemHeadingLevel,\n  hiddenUntilFound = true,\n  align = \"start\",\n  schemaOrg = false,\n  listAs,\n  itemAs,\n  headingLevel = 2,\n  as,\n  spacing = \"md\",\n  className,\n  id,\n}: FaqAccordionProps) {\n  const Wrapper = (listAs ?? \"div\") as ElementType\n  const Item = (itemAs ?? \"div\") as ElementType\n  const titleId = title ? (id ? `${id}-title` : slugId(title)) : undefined\n  const itemLevel = itemHeadingLevel ?? (Math.min(headingLevel + 1, 6) as HeadingLevel)\n\n  const structured = schemaOrg\n    ? items\n        .map((item) => ({\n          question: item.question,\n          text:\n            item.plainAnswer ??\n            (typeof item.answer === \"string\" ? item.answer : undefined),\n        }))\n        .filter((entry): entry is { question: string; text: string } =>\n          Boolean(entry.text),\n        )\n    : []\n\n  return (\n    <Section\n      as={as ?? (title ? \"section\" : \"div\")}\n      spacing={spacing}\n      labelledBy={titleId}\n      id={id}\n      className={className}\n    >\n      <Container size=\"md\" className=\"flex flex-col gap-12\">\n        {title ? (\n          <SectionHeader\n            align={align}\n            eyebrow={eyebrow}\n            title={title}\n            description={description}\n            headingLevel={headingLevel}\n            titleId={titleId}\n          />\n        ) : null}\n\n        <Accordion.Root\n          multiple={multiple}\n          defaultValue={defaultOpen}\n          hiddenUntilFound={hiddenUntilFound}\n          render={<Wrapper />}\n          className=\"divide-border border-border flex flex-col divide-y border-y\"\n        >\n          {items.map((item) => (\n            <Accordion.Item key={valueOf(item)} value={valueOf(item)} render={<Item />}>\n              <Accordion.Header render={<Heading level={itemLevel} />}>\n                <Accordion.Trigger\n                  className={cn(\n                    \"text-foreground group flex w-full items-center justify-between gap-4\",\n                    \"min-h-14 py-5 text-left text-base font-medium sm:text-lg\",\n                    \"duration-facade-fast ease-facade-out hover:text-foreground/80 cursor-pointer transition-colors\",\n                    \"focus-visible:ring-ring rounded-sm outline-none focus-visible:ring-2\",\n                  )}\n                >\n                  <span className=\"text-pretty\">{item.question}</span>\n                  <ChevronDownIcon\n                    aria-hidden\n                    focusable=\"false\"\n                    className=\"text-muted-foreground duration-facade-base ease-facade-out size-5 shrink-0 transition-transform group-data-[panel-open]:rotate-180\"\n                  />\n                </Accordion.Trigger>\n              </Accordion.Header>\n\n              <Accordion.Panel\n                className={cn(\n                  \"h-[var(--accordion-panel-height)] overflow-hidden\",\n                  \"duration-facade-base ease-facade-out transition-[height]\",\n                  \"data-[ending-style]:h-0 data-[starting-style]:h-0\",\n                )}\n              >\n                <div className=\"text-muted-foreground text-pretty pb-6 text-base\">\n                  {item.answer}\n                </div>\n              </Accordion.Panel>\n            </Accordion.Item>\n          ))}\n        </Accordion.Root>\n\n        {structured.length > 0 ? (\n          <script\n            type=\"application/ld+json\"\n            // Values come from the page's own content, not from user input.\n            dangerouslySetInnerHTML={{\n              __html: JSON.stringify({\n                \"@context\": \"https://schema.org\",\n                \"@type\": \"FAQPage\",\n                mainEntity: structured.map((entry) => ({\n                  \"@type\": \"Question\",\n                  name: entry.question,\n                  acceptedAnswer: { \"@type\": \"Answer\", text: entry.text },\n                })),\n              }),\n            }}\n          />\n        ) : null}\n      </Container>\n    </Section>\n  )\n}\n"
    }
  ],
  "docs": "Import styles/facade-tokens.css in your global stylesheet, after @import \"tailwindcss\". Set headingLevel to fit the page outline (sections default to h2). Pass next/link and next/image through the link and image props. Docs: https://facadeui.dev/components/faq-accordion"
}
