{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "accordion",
  "title": "Accordion",
  "description": "A vertically stacked set of interactive headings that each reveal a section of content, with a grid-rows unfold animation and full keyboard support.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/accordion.tsx",
      "type": "registry:ui",
      "content": "'use client'\n\nimport {\n  createContext,\n  forwardRef,\n  useCallback,\n  useContext,\n  useId,\n  useMemo,\n  useState,\n  type HTMLAttributes,\n  type ReactNode,\n} from 'react'\nimport { useReducedMotion } from 'motion/react'\nimport { cn } from '@/lib/utils'\n\ninterface AccordionContextValue {\n  type: 'single' | 'multiple'\n  collapsible: boolean\n  expandedValues: string[]\n  toggle: (value: string) => void\n  reducedMotion: boolean\n}\n\nconst AccordionContext = createContext<AccordionContextValue | null>(null)\n\nfunction useAccordionCtx(componentName: string) {\n  const ctx = useContext(AccordionContext)\n  if (!ctx) throw new Error(`${componentName} must be used within <Accordion>`)\n  return ctx\n}\n\ninterface AccordionItemContextValue {\n  value: string\n  isExpanded: boolean\n  disabled: boolean\n  triggerId: string\n  contentId: string\n}\n\nconst AccordionItemContext = createContext<AccordionItemContextValue | null>(null)\n\nfunction useAccordionItemCtx(componentName: string) {\n  const ctx = useContext(AccordionItemContext)\n  if (!ctx) throw new Error(`${componentName} must be used within <AccordionItem>`)\n  return ctx\n}\n\nexport interface AccordionProps extends Omit<HTMLAttributes<HTMLDivElement>, 'defaultValue'> {\n  type: 'single' | 'multiple'\n  collapsible?: boolean\n  value?: string | string[]\n  defaultValue?: string | string[]\n  onValueChange?: (value: string | string[]) => void\n  className?: string\n  children?: ReactNode\n}\n\nexport const Accordion = forwardRef<HTMLDivElement, AccordionProps>(\n  (\n    {\n      type,\n      collapsible = false,\n      value: valueProp,\n      defaultValue,\n      onValueChange,\n      className,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const isControlled = valueProp !== undefined\n    const reducedMotion = useReducedMotion() ?? false\n\n    const normalizeValue = useCallback((v: string | string[] | undefined): string[] => {\n      if (v === undefined) return []\n      if (Array.isArray(v)) return v\n      return v ? [v] : []\n    }, [])\n\n    const [internal, setInternal] = useState<string[]>(() => normalizeValue(defaultValue))\n    const expandedValues = isControlled ? normalizeValue(valueProp) : internal\n\n    const toggle = useCallback(\n      (itemValue: string) => {\n        const isExpanded = expandedValues.includes(itemValue)\n\n        let next: string[]\n\n        if (type === 'single') {\n          if (isExpanded) {\n            next = collapsible ? [] : expandedValues\n          } else {\n            next = [itemValue]\n          }\n        } else {\n          if (isExpanded) {\n            next = expandedValues.filter((v) => v !== itemValue)\n          } else {\n            next = [...expandedValues, itemValue]\n          }\n        }\n\n        if (!isControlled) setInternal(next)\n\n        if (onValueChange) {\n          onValueChange(type === 'single' ? (next[0] ?? '') : next)\n        }\n      },\n      [type, collapsible, expandedValues, isControlled, onValueChange],\n    )\n\n    const ctxValue = useMemo<AccordionContextValue>(\n      () => ({ type, collapsible, expandedValues, toggle, reducedMotion }),\n      [type, collapsible, expandedValues, toggle, reducedMotion],\n    )\n\n    return (\n      <AccordionContext.Provider value={ctxValue}>\n        <div ref={ref} className={cn('flex flex-col gap-2', className)} {...props}>\n          {children}\n        </div>\n      </AccordionContext.Provider>\n    )\n  },\n)\n\nAccordion.displayName = 'Accordion'\n\nexport interface AccordionItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'value'> {\n  value: string\n  disabled?: boolean\n  className?: string\n  children?: ReactNode\n}\n\nexport const AccordionItem = forwardRef<HTMLDivElement, AccordionItemProps>(\n  ({ value, disabled = false, className, children, ...props }, ref) => {\n    const { expandedValues } = useAccordionCtx('AccordionItem')\n    const isExpanded = expandedValues.includes(value)\n    const uid = useId()\n    const triggerId = `accordion-trigger-${uid}`\n    const contentId = `accordion-content-${uid}`\n\n    const itemCtx = useMemo<AccordionItemContextValue>(\n      () => ({ value, isExpanded, disabled, triggerId, contentId }),\n      [value, isExpanded, disabled, triggerId, contentId],\n    )\n\n    return (\n      <AccordionItemContext.Provider value={itemCtx}>\n        <div\n          ref={ref}\n          data-state={isExpanded ? 'open' : 'closed'}\n          data-disabled={disabled || undefined}\n          className={cn(\n            'rounded-lg squircle-corners border border-(--color-border) bg-(--color-card) overflow-hidden',\n            disabled && 'opacity-50 pointer-events-none',\n            className,\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </AccordionItemContext.Provider>\n    )\n  },\n)\n\nAccordionItem.displayName = 'AccordionItem'\n\nexport interface AccordionTriggerProps extends Omit<HTMLAttributes<HTMLButtonElement>, 'children'> {\n  className?: string\n  children?: ReactNode\n  headingLevel?: 2 | 3 | 4 | 5 | 6\n}\n\nexport const AccordionTrigger = forwardRef<HTMLButtonElement, AccordionTriggerProps>(\n  ({ className, children, headingLevel = 3, ...props }, ref) => {\n    const { toggle } = useAccordionCtx('AccordionTrigger')\n    const { value, isExpanded, disabled, triggerId, contentId } =\n      useAccordionItemCtx('AccordionTrigger')\n\n    const Heading = `h${headingLevel}` as const\n\n    return (\n      <Heading className=\"m-0 flex\">\n        <button\n          ref={ref}\n          type=\"button\"\n          id={triggerId}\n          aria-expanded={isExpanded}\n          aria-controls={contentId}\n          disabled={disabled}\n          onClick={() => toggle(value)}\n          className={cn(\n            'flex flex-1 items-center justify-between gap-2 px-4 py-3 text-start font-medium text-(--color-fg) outline-none',\n            'transition-colors duration-(--motion-dur-fast) ease-(--motion-ease-in-out)',\n            'hover:bg-(--color-muted)/50',\n            'focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-inset',\n            'motion-reduce:transition-none',\n            className,\n          )}\n          {...props}\n        >\n          <span className=\"flex-1\">{children}</span>\n          <svg\n            xmlns=\"http://www.w3.org/2000/svg\"\n            width=\"16\"\n            height=\"16\"\n            viewBox=\"0 0 24 24\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            aria-hidden=\"true\"\n            className={cn(\n              'shrink-0 text-(--color-fg)/60',\n              'transition-transform duration-(--motion-dur-base) ease-(--motion-ease-in-out)',\n              'motion-reduce:transition-none motion-reduce:transform-none',\n              isExpanded && 'rotate-180',\n            )}\n          >\n            <polyline points=\"6 9 12 15 18 9\" />\n          </svg>\n        </button>\n      </Heading>\n    )\n  },\n)\n\nAccordionTrigger.displayName = 'AccordionTrigger'\n\nexport interface AccordionContentProps extends HTMLAttributes<HTMLDivElement> {\n  className?: string\n  children?: ReactNode\n}\n\nexport const AccordionContent = forwardRef<HTMLDivElement, AccordionContentProps>(\n  ({ className, children, ...props }, ref) => {\n    const { reducedMotion } = useAccordionCtx('AccordionContent')\n    const { isExpanded, triggerId, contentId } = useAccordionItemCtx('AccordionContent')\n\n    return (\n      <div\n        ref={ref}\n        id={contentId}\n        role=\"region\"\n        aria-labelledby={triggerId}\n        hidden={!isExpanded && reducedMotion}\n        className={cn(\n          'grid',\n          'transition-[grid-template-rows] duration-(--motion-dur-base) ease-(--motion-ease-in-out)',\n          'motion-reduce:transition-none',\n          isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',\n        )}\n      >\n        <div\n          className={cn(\n            'overflow-hidden',\n            'transition-[opacity,transform] duration-(--motion-dur-base) ease-(--motion-ease-in-out)',\n            'motion-reduce:transition-none motion-reduce:transform-none',\n            isExpanded ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-1',\n          )}\n        >\n          <div className={cn('px-4 pb-3 pt-0 text-sm text-(--color-fg)/80', className)} {...props}>\n            {children}\n          </div>\n        </div>\n      </div>\n    )\n  },\n)\n\nAccordionContent.displayName = 'AccordionContent'\n\nexport function AccordionPreview() {\n  return (\n    <div className=\"flex w-full max-w-md items-center justify-center p-6\">\n      <Accordion type=\"single\" collapsible defaultValue=\"item-1\" className=\"w-full\">\n        <AccordionItem value=\"item-1\">\n          <AccordionTrigger>Is it accessible?</AccordionTrigger>\n          <AccordionContent>\n            Yes. It adheres to the WAI-ARIA design pattern with proper aria-expanded, aria-controls,\n            and role=&quot;region&quot; attributes.\n          </AccordionContent>\n        </AccordionItem>\n        <AccordionItem value=\"item-2\">\n          <AccordionTrigger>Is it animated?</AccordionTrigger>\n          <AccordionContent>\n            Yes. It uses a grid-rows transition with a subtle y-settle for a polished unfold effect.\n            Respects prefers-reduced-motion.\n          </AccordionContent>\n        </AccordionItem>\n        <AccordionItem value=\"item-3\">\n          <AccordionTrigger>Can I use it controlled?</AccordionTrigger>\n          <AccordionContent>\n            Yes. Pass value and onValueChange for full controlled state, or use defaultValue for\n            uncontrolled.\n          </AccordionContent>\n        </AccordionItem>\n      </Accordion>\n    </div>\n  )\n}\n"
    }
  ],
  "type": "registry:ui"
}
