Accordion

A vertically stacked set of interactive headings that each reveal a section of content.

Basic

Multiple

With Borders

In Card
Subscription & Billing
Common questions about your account, plans, and payments

We offer three subscription tiers: Starter ($9/month), Professional ($29/month), and Enterprise ($99/month). Each plan includes increasing storage limits, API access, priority support, and team collaboration features.

Annual billing is available with a 20% discount. All plans include a 14-day free trial with no credit card required.

With Disabled

Installation

Install the dependencies

pnpm add cn class-variance-authority github:sveltery/base

Copy the component

Copy the following files into src/lib/components/ui/accordion.

src/lib/components/ui/accordion/Accordion.svelte
<script lang="ts">
  import type { ComponentProps } from 'svelte';
  import { Accordion as Primitive } from '@sveltery/base/accordion';
  import { cn } from 'cn';
  let { value = $bindable(), children, class: classProp, ...props }: ComponentProps<typeof Primitive.Root> = $props();
</script>
<Primitive.Root data-slot="accordion" bind:value {...props} class={cn('flex w-full flex-col', classProp)}>
  {@render children?.()}
</Primitive.Root>
src/lib/components/ui/accordion/AccordionContent.svelte
<script lang="ts">
  import type { ComponentProps } from 'svelte';
  import { Accordion as Primitive } from '@sveltery/base/accordion';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Panel> = $props();
</script>
<Primitive.Panel data-slot="accordion-content" {...props} class="data-open:animate-accordion-down data-closed:animate-accordion-up text-sm overflow-hidden">
  <div class={cn('pt-0 pb-2.5 h-(--accordion-panel-height) data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4', classProp)}>
    {@render children?.()}
  </div>
</Primitive.Panel>
src/lib/components/ui/accordion/AccordionItem.svelte
<script lang="ts">
  import type { ComponentProps } from 'svelte';
  import { Accordion as Primitive } from '@sveltery/base/accordion';
  import { cn } from 'cn';
  let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Item> = $props();
</script>
<Primitive.Item data-slot="accordion-item" {children} {...props} class={cn('not-last:border-b', classProp)} />
src/lib/components/ui/accordion/AccordionTrigger.svelte
<script lang="ts">
  import type { ComponentProps } from 'svelte';
  import { Accordion as Primitive } from '@sveltery/base/accordion';
  import { cn } from 'cn';
  import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
  import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
  let { children, class: classProp, ...props }: ComponentProps<typeof Primitive.Trigger> = $props();
</script>
<Primitive.Header class="flex">
  <Primitive.Trigger data-slot="accordion-trigger" {...props} class={cn('focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-lg py-2.5 text-left text-sm font-medium hover:underline focus-visible:ring-3 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none aria-disabled:pointer-events-none aria-disabled:opacity-50', classProp)}>
    {@render children?.()}
    <ChevronDownIcon data-slot="accordion-trigger-icon" class="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" />
    <ChevronUpIcon data-slot="accordion-trigger-icon" class="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" />
  </Primitive.Trigger>
</Primitive.Header>
src/lib/components/ui/accordion/index.ts
export { default as Accordion } from './Accordion.svelte';
export { default as AccordionItem } from './AccordionItem.svelte';
export { default as AccordionTrigger } from './AccordionTrigger.svelte';
export { default as AccordionContent } from './AccordionContent.svelte';

Update the import paths

Change the imports to match your project setup.

Usage

<script lang="ts">
  import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '$lib/components/ui/accordion';
</script>
<Accordion>
  <AccordionItem value="item-1">
    <AccordionTrigger>Is it accessible?</AccordionTrigger>
    <AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
  </AccordionItem>
</Accordion>

Composition

Accordion
├── AccordionItem
│   ├── AccordionTrigger
│   └── AccordionContent
└── AccordionItem
    ├── AccordionTrigger
    └── AccordionContent

Open items

Accordion holds the values of its open items in value. Pass it, or bind it to read and set the open items.

<script lang="ts">
  let value = $state(['item-1']);
</script>

<Accordion bind:value>
  <AccordionItem value="item-1">…</AccordionItem>
  <AccordionItem value="item-2">…</AccordionItem>
</Accordion>

Multiple

Set multiple to let several items stay open at the same time. Without it, opening one item closes the others.

<Accordion multiple>…</Accordion>

Disabled

Set disabled on an AccordionItem to disable that item.

<AccordionItem value="item-2" disabled>…</AccordionItem>

API reference

Accordion is styled on top of the Sveltery Base Accordion primitive, which owns its behavior, open state and accessibility. See the Base UI Accordion documentation for the behavior it ports.