{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "accordion",
  "title": "Accordion",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/accordion/Accordion.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface AccordionProps extends HTMLAttributes<HTMLDivElement> {\n    variant?: 'default' | 'separated' | 'ghost'\n    type?: 'single' | 'multiple'\n    /** Controlled open value. Pair with `bind:value`. */\n    value?: string | string[] | undefined\n    /** Uncontrolled initial value. */\n    defaultValue?: string | string[]\n    collapsible?: boolean\n    disabled?: boolean\n    dir?: 'ltr' | 'rtl'\n    orientation?: 'horizontal' | 'vertical'\n    /** Render your own root element with the accordion's props and styles. */\n    child?: Snippet<[{ props: Record<string, unknown> }]>\n    ref?: HTMLDivElement | null\n    onValueChange?: (value: string | string[] | undefined) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { setContext } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { accordionVariants } from './accordion.variants'\n  import { ACCORDION_ROOT_KEY, type AccordionRootContext } from './context'\n\n  let {\n    class: className,\n    variant = 'default',\n    type = 'single',\n    value = $bindable(),\n    defaultValue,\n    collapsible = false,\n    disabled = false,\n    dir,\n    orientation = 'vertical',\n    child,\n    children,\n    ref = $bindable(null),\n    onValueChange,\n    ...restProps\n  }: AccordionProps = $props()\n\n  function normalizeSingle(v: string | string[] | undefined): string | undefined {\n    return Array.isArray(v) ? v[0] : v\n  }\n\n  function normalizeMultiple(v: string | string[] | undefined): string[] {\n    if (v === undefined) return []\n    return Array.isArray(v) ? [...v] : [v]\n  }\n\n  // Snapshot once: later `type` / `defaultValue` changes must not reset user state.\n  const getInitialState = () => {\n    const initial = value ?? defaultValue\n    return {\n      single: type === 'single' ? normalizeSingle(initial) : undefined,\n      multiple: type === 'multiple' ? normalizeMultiple(initial) : [],\n    }\n  }\n  const initialState = getInitialState()\n  let internalSingle = $state<string | undefined>(initialState.single)\n  let internalMultiple = $state<string[]>(initialState.multiple)\n\n  // Controlled mode: a bound `value` always wins over internal state.\n  $effect.pre(() => {\n    if (value === undefined) return\n    if (type === 'single') internalSingle = normalizeSingle(value)\n    else internalMultiple = normalizeMultiple(value)\n  })\n\n  function isOpen(itemValue: string): boolean {\n    return type === 'single' ? internalSingle === itemValue : internalMultiple.includes(itemValue)\n  }\n\n  function toggle(itemValue: string) {\n    if (type === 'single') {\n      const next = internalSingle === itemValue ? (collapsible ? undefined : itemValue) : itemValue\n      internalSingle = next\n      value = next\n      onValueChange?.(next)\n    } else {\n      const next = internalMultiple.includes(itemValue)\n        ? internalMultiple.filter((v) => v !== itemValue)\n        : [...internalMultiple, itemValue]\n      internalMultiple = next\n      value = next\n      onValueChange?.(next)\n    }\n  }\n\n  // Trigger registry for WAI-ARIA arrow-key navigation. Registration order\n  // follows mount order, which matches DOM order for static item lists.\n  const triggers: HTMLElement[] = []\n\n  function registerTrigger(el: HTMLElement) {\n    if (!triggers.includes(el)) triggers.push(el)\n  }\n\n  function unregisterTrigger(el: HTMLElement) {\n    const i = triggers.indexOf(el)\n    if (i >= 0) triggers.splice(i, 1)\n  }\n\n  function focusSiblingTrigger(current: HTMLElement, target: 1 | -1 | 'first' | 'last') {\n    if (triggers.length === 0) return\n    const i = triggers.indexOf(current)\n    if (target === 'first') triggers[0]?.focus()\n    else if (target === 'last') triggers[triggers.length - 1]?.focus()\n    else triggers[(i + target + triggers.length) % triggers.length]?.focus()\n  }\n\n  setContext<AccordionRootContext>(ACCORDION_ROOT_KEY, {\n    get variant() {\n      return variant\n    },\n    get type() {\n      return type\n    },\n    get collapsible() {\n      return collapsible\n    },\n    get disabled() {\n      return disabled\n    },\n    get orientation() {\n      return orientation\n    },\n    isOpen,\n    toggle,\n    registerTrigger,\n    unregisterTrigger,\n    focusSiblingTrigger,\n  })\n\n  const mergedProps = $derived({\n    'data-uipkge': '',\n    'data-slot': 'accordion',\n    'data-variant': variant,\n    'data-orientation': orientation,\n    dir,\n    class: cn(accordionVariants({ variant }), className),\n    ...restProps,\n  })\n</script>\n\n{#if child}\n  {@render child({ props: mergedProps })}\n{:else}\n  <div bind:this={ref} {...mergedProps}>{@render children?.()}</div>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/accordion/Accordion.svelte"
    },
    {
      "path": "packages/registry-svelte/components/accordion/AccordionContent.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface AccordionContentProps extends HTMLAttributes<HTMLDivElement> {\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getAccordionItem, getAccordionRoot } from './context'\n\n  let { class: className, children, ref = $bindable(null), ...restProps }: AccordionContentProps = $props()\n\n  const root = getAccordionRoot()\n  const item = getAccordionItem()\n\n  const open = $derived(item?.open ?? true)\n  const orientation = $derived(root?.orientation ?? 'vertical')\n</script>\n\n<div\n  bind:this={ref}\n  role=\"region\"\n  id={item?.contentId}\n  aria-labelledby={item?.triggerId}\n  data-uipkge=\"\"\n  data-slot=\"accordion-content\"\n  data-state={open ? 'open' : 'closed'}\n  data-orientation={orientation}\n  hidden={!open}\n  class={cn(\n    'text-muted-foreground overflow-hidden text-sm',\n    // Height via CSS vars + tw-animate-css (not height:auto).\n    // duration/ease set --tw-duration/--tw-ease consumed by the utility.\n    'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',\n    'duration-200 ease-[cubic-bezier(0.32,0.72,0,1)]',\n    'motion-reduce:animate-none',\n    className,\n  )}\n  {...restProps}\n>\n  <div class=\"pt-0 pb-4\">\n    {@render children?.()}\n  </div>\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/accordion/AccordionContent.svelte"
    },
    {
      "path": "packages/registry-svelte/components/accordion/AccordionHeader.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface AccordionHeaderProps extends HTMLAttributes<HTMLElement> {\n    ref?: HTMLElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n\n  let { class: className, children, ref = $bindable(null), ...restProps }: AccordionHeaderProps = $props()\n</script>\n\n<h3 bind:this={ref} data-uipkge=\"\" data-slot=\"accordion-header\" class={cn('flex', className)} {...restProps}>\n  {@render children?.()}\n</h3>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/accordion/AccordionHeader.svelte"
    },
    {
      "path": "packages/registry-svelte/components/accordion/AccordionItem.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface AccordionItemProps extends HTMLAttributes<HTMLDivElement> {\n    value: string\n    disabled?: boolean\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { setContext } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { accordionItemVariants } from './accordion.variants'\n  import { ACCORDION_ITEM_KEY, getAccordionRoot, type AccordionItemContext } from './context'\n\n  let { value, disabled = false, class: className, children, ref = $bindable(null), ...restProps }: AccordionItemProps =\n    $props()\n\n  const root = getAccordionRoot()\n\n  const open = $derived(root?.isOpen(value) ?? true)\n  const variant = $derived(root?.variant ?? 'default')\n  const orientation = $derived(root?.orientation ?? 'vertical')\n  const isDisabled = $derived(disabled || (root?.disabled ?? false))\n\n  const itemId = $props.id()\n  const triggerId = `${itemId}-trigger`\n  const contentId = `${itemId}-content`\n\n  setContext<AccordionItemContext>(ACCORDION_ITEM_KEY, {\n    get value() {\n      return value\n    },\n    get triggerId() {\n      return triggerId\n    },\n    get contentId() {\n      return contentId\n    },\n    get open() {\n      return open\n    },\n    get disabled() {\n      return isDisabled\n    },\n  })\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge=\"\"\n  data-slot=\"accordion-item\"\n  data-state={open ? 'open' : 'closed'}\n  data-disabled={isDisabled ? '' : undefined}\n  data-orientation={orientation}\n  class={cn(accordionItemVariants({ variant }), className)}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/accordion/AccordionItem.svelte"
    },
    {
      "path": "packages/registry-svelte/components/accordion/AccordionTrigger.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  export interface AccordionTriggerProps extends HTMLButtonAttributes {\n    /** Render your own trigger element with the trigger's props and styles. */\n    child?: Snippet<[{ props: Record<string, unknown> }]>\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import type { KeyboardEventHandler, MouseEventHandler } from 'svelte/elements'\n  import { ChevronDown } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { accordionTriggerVariants } from './accordion.variants'\n  import { getAccordionItem, getAccordionRoot } from './context'\n\n  let {\n    class: className,\n    child,\n    children,\n    ref = $bindable(null),\n    disabled = false,\n    onclick,\n    onkeydown,\n    ...restProps\n  }: AccordionTriggerProps = $props()\n\n  const root = getAccordionRoot()\n  const item = getAccordionItem()\n\n  const open = $derived(item?.open ?? true)\n  const variant = $derived(root?.variant ?? 'default')\n  const orientation = $derived(root?.orientation ?? 'vertical')\n  const isDisabled = $derived(disabled || (item?.disabled ?? false))\n\n  $effect(() => {\n    const el = ref\n    if (!el) return\n    root?.registerTrigger(el)\n    return () => root?.unregisterTrigger(el)\n  })\n\n  const handleClick: MouseEventHandler<HTMLButtonElement> = (event) => {\n    onclick?.(event)\n    if (event.defaultPrevented || isDisabled || !item) return\n    root?.toggle(item.value)\n  }\n\n  const handleKeydown: KeyboardEventHandler<HTMLButtonElement> = (event) => {\n    onkeydown?.(event)\n    if (event.defaultPrevented || !ref) return\n    const vertical = orientation !== 'horizontal'\n    let target: 1 | -1 | 'first' | 'last' | null = null\n    if (event.key === 'Home') target = 'first'\n    else if (event.key === 'End') target = 'last'\n    else if (vertical && event.key === 'ArrowDown') target = 1\n    else if (vertical && event.key === 'ArrowUp') target = -1\n    else if (!vertical && event.key === 'ArrowRight') target = 1\n    else if (!vertical && event.key === 'ArrowLeft') target = -1\n    if (target !== null) {\n      event.preventDefault()\n      root?.focusSiblingTrigger(ref, target)\n    }\n  }\n\n  const mergedProps = $derived({\n    'data-uipkge': '',\n    'data-slot': 'accordion-trigger',\n    'data-state': open ? ('open' as const) : ('closed' as const),\n    'data-disabled': isDisabled ? '' : undefined,\n    'data-orientation': orientation,\n    id: item?.triggerId,\n    'aria-expanded': open,\n    'aria-controls': item?.contentId,\n    disabled: isDisabled,\n    type: 'button' as const,\n    class: cn(accordionTriggerVariants({ variant }), className),\n    onclick: handleClick,\n    onkeydown: handleKeydown,\n    ...restProps,\n  })\n</script>\n\n{#if child}\n  {@render child({ props: mergedProps })}\n{:else}\n  <button bind:this={ref} {...mergedProps}>\n    {@render children?.()}\n    <ChevronDown\n      class=\"text-muted-foreground size-4 shrink-0 transition-transform duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[state=open]/accordion-trigger:rotate-180 motion-reduce:transition-none\"\n      aria-hidden=\"true\"\n    />\n  </button>\n{/if}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/accordion/AccordionTrigger.svelte"
    },
    {
      "path": "packages/registry-svelte/components/accordion/accordion.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts`) so consuming Vue SFCs can import without creating a circular\n * dependency through the index. See card.variants.ts for the canonical\n * example + the SSR symptom that motivated the split.\n */\n\nexport const accordionVariants = cva('w-full', {\n  variants: {\n    variant: {\n      // Bottom-border between items (the classic shadcn look).\n      default: '',\n      // Each item is its own bordered card with a small gap between them.\n      separated: 'space-y-2',\n      // Borderless. Use when the parent container already provides framing.\n      ghost: '',\n    },\n  },\n  defaultVariants: {\n    variant: 'default',\n  },\n})\n\nexport const accordionItemVariants = cva('', {\n  variants: {\n    variant: {\n      default: 'border-b border-border last:border-b-0',\n      separated: 'rounded-md border border-border bg-card overflow-hidden',\n      ghost: '',\n    },\n  },\n  defaultVariants: {\n    variant: 'default',\n  },\n})\n\nexport const accordionTriggerVariants = cva(\n  'group/accordion-trigger flex w-full items-center justify-between gap-3 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/40 hover:text-primary disabled:pointer-events-none disabled:opacity-50',\n  {\n    variants: {\n      variant: {\n        default: 'py-4',\n        separated: 'px-4 py-3 hover:bg-muted/50',\n        ghost: 'py-3',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  },\n)\n\nexport type AccordionVariants = VariantProps<typeof accordionVariants>\n\nexport type AccordionItemVariants = VariantProps<typeof accordionItemVariants>\n\nexport type AccordionTriggerVariants = VariantProps<typeof accordionTriggerVariants>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/accordion/accordion.variants.ts"
    },
    {
      "path": "packages/registry-svelte/components/accordion/context.ts",
      "content": "import { getContext, hasContext } from 'svelte'\n\nexport type AccordionVariant = 'default' | 'separated' | 'ghost'\nexport type AccordionType = 'single' | 'multiple'\n\n/**\n * Root state shared with items via `setContext` / `getContext` (Svelte\n * counterpart of Vue's provide/inject). Implemented with plain getters that\n * close over the root component's `$state`, so reads inside a child's\n * `$derived` stay reactive without a `.svelte.ts` module.\n */\nexport interface AccordionRootContext {\n  readonly variant: AccordionVariant\n  readonly type: AccordionType\n  readonly collapsible: boolean\n  readonly disabled: boolean\n  readonly orientation: 'horizontal' | 'vertical'\n  isOpen: (value: string) => boolean\n  toggle: (value: string) => void\n  registerTrigger: (el: HTMLElement) => void\n  unregisterTrigger: (el: HTMLElement) => void\n  focusSiblingTrigger: (current: HTMLElement, target: 1 | -1 | 'first' | 'last') => void\n}\n\nexport interface AccordionItemContext {\n  readonly value: string\n  readonly triggerId: string\n  readonly contentId: string\n  readonly open: boolean\n  readonly disabled: boolean\n}\n\nexport const ACCORDION_ROOT_KEY = Symbol('uipkge-accordion-root')\nexport const ACCORDION_ITEM_KEY = Symbol('uipkge-accordion-item')\n\nexport function getAccordionRoot(): AccordionRootContext | undefined {\n  return hasContext(ACCORDION_ROOT_KEY) ? getContext<AccordionRootContext>(ACCORDION_ROOT_KEY) : undefined\n}\n\nexport function getAccordionItem(): AccordionItemContext | undefined {\n  return hasContext(ACCORDION_ITEM_KEY) ? getContext<AccordionItemContext>(ACCORDION_ITEM_KEY) : undefined\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/accordion/context.ts"
    },
    {
      "path": "packages/registry-svelte/components/accordion/index.ts",
      "content": "export { default as Accordion, type AccordionProps } from './Accordion.svelte'\nexport { default as AccordionContent, type AccordionContentProps } from './AccordionContent.svelte'\nexport { default as AccordionHeader, type AccordionHeaderProps } from './AccordionHeader.svelte'\nexport { default as AccordionItem, type AccordionItemProps } from './AccordionItem.svelte'\nexport { default as AccordionTrigger, type AccordionTriggerProps } from './AccordionTrigger.svelte'\n\n// Re-export variant API from the sibling file (kept separate to avoid the\n// Component.svelte <-> index.ts circular import that broke dev SSR for Card).\nexport {\n  accordionVariants,\n  accordionItemVariants,\n  accordionTriggerVariants,\n  type AccordionVariants,\n  type AccordionItemVariants,\n  type AccordionTriggerVariants,\n} from './accordion.variants'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/accordion/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority",
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Vertically stacked, collapsible panels — one or many open at a time. Use for FAQs, settings groups, and any place where space is tight but content needs to stay browsable. Hand-rolled runes state with WAI-ARIA keyboard support (arrows/Home/End), smooth animation, controlled or uncontrolled value.",
  "categories": [
    "disclosure"
  ]
}