Accordion

Organizes content into collapsible sections.

	<script lang="ts">
  import { Accordion } from "bits-ui";
  import CaretDown from "phosphor-svelte/lib/CaretDown";
 
  const items = [
    {
      value: "1",
      title: "What is the meaning of life?",
      content:
        "To become a better person, to help others, and to leave the world a better place than you found it."
    },
    {
      value: "2",
      title: "How do I become a better person?",
      content:
        "Read books, listen to podcasts, and surround yourself with people who inspire you."
    },
    {
      value: "3",
      title: "What is the best way to help others?",
      content: "Give them your time, attention, and love."
    }
  ];
</script>
 
<Accordion.Root class="w-full sm:max-w-[70%]" type="multiple">
  {#each items as item (item.value)}
    <Accordion.Item
      value={item.value}
      class="border-dark-10 group border-b px-1.5"
    >
      <Accordion.Header>
        <Accordion.Trigger
          class="flex w-full flex-1 select-none items-center justify-between py-5 text-[15px] font-medium transition-all [&[data-state=open]>span>svg]:rotate-180"
        >
          <span class="w-full text-left">
            {item.title}
          </span>
          <span
            class="hover:bg-dark-10 inline-flex size-8 items-center justify-center rounded-[7px] bg-transparent"
          >
            <CaretDown class="size-[18px] transition-transform duration-200" />
          </span>
        </Accordion.Trigger>
      </Accordion.Header>
      <Accordion.Content
        class="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm tracking-[-0.01em]"
      >
        <div class="pb-[25px]">
          {item.content}
        </div>
      </Accordion.Content>
    </Accordion.Item>
  {/each}
</Accordion.Root>

Overview

The Accordion component is a versatile UI element designed to organize content into collapsible sections, helping users focus on specific information without being overwhelmed by visual clutter.

Quick Start

	<script lang="ts">
  import { Accordion } from "bits-ui";
</script>
 
<Accordion.Root type="single">
  <Accordion.Item value="item-1">
    <Accordion.Header>
      <Accordion.Trigger>Item 1 Title</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content
      >This is the collapsible content for this section.</Accordion.Content
    >
  </Accordion.Item>
  <Accordion.Item value="item-2">
    <Accordion.Header>
      <Accordion.Trigger>Item 2 Title</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content
      >This is the collapsible content for this section.</Accordion.Content
    >
  </Accordion.Item>
</Accordion.Root>

Key Features

  • Single or Multiple Mode: Toggle between allowing one open section or multiple sections at once.
  • Accessible by Default: Built-in ARIA attributes and keyboard navigation support.
  • Smooth Transitions: Leverage CSS variables or Svelte transitions for animated open/close effects.
  • Flexible State: Use uncontrolled defaults or take full control with bound values.

Structure

The Accordion is a compound component made up of several parts:

  • Accordion.Root: Container that manages overall state
  • Accordion.Item: Individual collapsible section
  • Accordion.Header: Contains the visible heading
  • Accordion.Trigger: The clickable element that toggles content visibility
  • Accordion.Content: The collapsible body content

Reusable Components

To streamline usage in larger applications, create custom wrapper components for repeated patterns. Below is an example of a reusable MyAccordionItem and MyAccordion.

Item Wrapper

Combines Item, Header, Trigger, and Content into a single component:

MyAccordionItem.svelte
	<script lang="ts">
  import { Accordion, type WithoutChildrenOrChild } from "bits-ui";
 
  type Props = WithoutChildrenOrChild<Accordion.ItemProps> & {
    title: string;
    content: string;
  };
 
  let { title, content, ...restProps }: Props = $props();
</script>
 
<Accordion.Item {...restProps}>
  <Accordion.Header>
    <Accordion.Trigger>{title}</Accordion.Trigger>
  </Accordion.Header>
  <Accordion.Content>
    {content}
  </Accordion.Content>
</Accordion.Item>

Accordion Wrapper

Wraps Root and renders multiple MyAccordionItem components:

MyAccordion.svelte
	<script lang="ts">
  import { Accordion, type WithoutChildrenOrChild } from "bits-ui";
  import MyAccordionItem from "$lib/components/MyAccordionItem.svelte";
 
  type Item = {
    value?: string;
    title: string;
    content: string;
    disabled?: boolean;
  };
 
  let {
    value = $bindable(),
    ref = $bindable(null),
    items,
    ...restProps
  }: WithoutChildrenOrChild<Accordion.RootProps> & {
    items: Item[];
  } = $props();
</script>
 
<!--
 Since we have to destructure the `value` to make it `$bindable`, we need to use `as any` here to avoid
 type errors from the discriminated union of `"single" | "multiple"`.
 (an unfortunate consequence of having to destructure bindable values)
  -->
<Accordion.Root bind:value bind:ref {...restProps as any}>
  {#each items as item, i (item.title + i)}
    <MyAccordionItem {...item} />
  {/each}
</Accordion.Root>

Usage Example

+page.svelte
	<script lang="ts">
  import MyAccordion from "$lib/components/MyAccordion.svelte";
  const items = [
    { title: "Item 1", content: "Content 1" },
    { title: "Item 2", content: "Content 2" },
  ];
</script>
 
<MyAccordion type="single" {items} />

Managing Value State

This section covers how to manage the value state of the Accordion.

Two-Way Binding

Use bind:value for simple, automatic state synchronization:

	<script lang="ts">
  import { Accordion } from "bits-ui";
  let myValue = $state<string[]>([]);
  const numberOfItemsOpen = $derived(myValue.length);
</script>
 
<button
  onclick={() => {
    myValue = ["item-1", "item-2"];
  }}
>
  Open Items 1 and 2
</button>
 
<Accordion.Root type="multiple" bind:value={myValue}>
  <Accordion.Item value="item-1">
    <!-- ... -->
  </Accordion.Item>
  <Accordion.Item value="item-2">
    <!-- ... -->
  </Accordion.Item>
  <Accordion.Item value="item-3">
    <!-- ... -->
  </Accordion.Item>
</Accordion.Root>

Fully Controlled

Use a Function Binding for complete control over the state's reads and writes.

	<script lang="ts">
  import { Accordion } from "bits-ui";
  let myValue = $state("");
 
  function getValue() {
    return myValue;
  }
 
  function setValue(newValue: string) {
    myValue = newValue;
  }
</script>
 
<Accordion.Root type="single" bind:value={getValue, setValue}>
  <!-- ... -->
</Accordion.Root>

See the State Management documentation for more information.

Customization

Single vs. Multiple

Set the type prop to "single" to allow only one accordion item to be open at a time.

	<MyAccordion
  type="single"
  items={[
    { title: "Title A", content: "Content A" },
    { title: "Title B", content: "Content B" },
    { title: "Title C", content: "Content C" },
  ]}
/>

Set the type prop to "multiple" to allow multiple accordion items to be open at the same time.

	<MyAccordion
  type="multiple"
  items={[
    { title: "Title A", content: "Content A" },
    { title: "Title B", content: "Content B" },
    { title: "Title C", content: "Content C" },
  ]}
/>

Default Open Items

Set the value prop to pre-open items:

	<MyAccordion value={["A", "C"]} type="multiple" />
Content A
Content C

Disable Items

Disable specific items with the disabled prop:

	<Accordion.Root type="single">
  <Accordion.Item value="item-1" disabled>
    <!-- ... -->
  </Accordion.Item>
</Accordion.Root>

Hidden Until Found

The hiddenUntilFound prop enables browser search functionality within collapsed accordion content. When enabled, collapsed content is marked with hidden="until-found", allowing browsers to automatically expand accordion items when users search for text within them.

	<Accordion.Root type="single">
  <Accordion.Item value="item-1">
    <Accordion.Header>
      <Accordion.Trigger>Search Demo</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content hiddenUntilFound>
      This content can be found by browser search (Ctrl+F/CMD+F) even when the
      accordion is closed. The accordion will automatically open when the
      browser finds matching text.
    </Accordion.Content>
  </Accordion.Item>
</Accordion.Root>

Svelte Transitions

The Accordion component can be enhanced with Svelte's built-in transition effects or other animation libraries.

Using forceMount and child Snippets

To apply Svelte transitions to Accordion components, use the forceMount prop in combination with the child snippet. This approach gives you full control over the mounting behavior and animation of the Accordion.Content.

	<Accordion.Content forceMount={true}>
  {#snippet child({ props, open })}
    {#if open}
      <div {...props} transition:slide={{ duration: 1000 }}>
        This is the accordion content that will transition in and out.
      </div>
    {/if}
  {/snippet}
</Accordion.Content>

In this example:

  • The forceMount prop ensures the components are always in the DOM.
  • The child snippet provides access to the open state and component props.
  • Svelte's #if block controls when the content is visible.
  • Transition directives (transition:fade and transition:fly) apply the animations.

Best Practices

For cleaner code and better maintainability, consider creating custom reusable components that encapsulate this transition logic.

MyAccordionContent.svelte
	<script lang="ts">
  import { Accordion, type WithoutChildrenOrChild } from "bits-ui";
  import type { Snippet } from "svelte";
  import { fade } from "svelte/transition";
 
  let {
    ref = $bindable(null),
    duration = 200,
    children,
    ...restProps
  }: WithoutChildrenOrChild<Accordion.ContentProps> & {
    duration?: number;
    children: Snippet;
  } = $props();
</script>
 
<Accordion.Content forceMount bind:ref {...restProps}>
  {#snippet child({ props, open })}
    {#if open}
      <div {...props} transition:fade={{ duration }}>
        {@render children?.()}
      </div>
    {/if}
  {/snippet}
</Accordion.Content>

You can then use the MyAccordionContent component alongside the other Accordion primitives throughout your application:

	<Accordion.Root>
  <Accordion.Item value="A">
    <Accordion.Header>
      <Accordion.Trigger>A</Accordion.Trigger>
    </Accordion.Header>
    <MyAccordionContent duration={300}>
      <!-- ... -->
    </MyAccordionContent>
  </Accordion.Item>
</Accordion.Root>

Examples

The following examples demonstrate different ways to use the Accordion component.

Horizontal Cards

Use the Accordion component to create a horizontal card layout with collapsible sections.

Mountain Range
Majestic mountain ranges with snow-capped peaks and lush valleys.
Ocean Views
Serene ocean scenes with crashing waves, beautiful sunsets, and sandy beaches.
Forest Retreats
Dense forests with towering trees, abundant wildlife, and peaceful streams.

Checkout Steps

Use the Accordion component to create a multi-step checkout process.

API Reference

Accordion.Root

The root accordion component used to set and manage the state of the accordion.

Property Details
type
value
onValueChange
disabled
loop
orientation
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-accordion-root

Accordion.Item

An accordion item.

Property Details
disabled
value
ref
children
child
Data Attribute Details
data-state
data-disabled
data-orientation
data-accordion-item

The header of the accordion item.

Property Details
level
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-heading-level
data-accordion-header

Accordion.Trigger

The button responsible for toggling the accordion item.

Property Details
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-accordion-trigger

Accordion.Content

The accordion item content, which is displayed when the item is open.

Property Details
forceMount
hiddenUntilFound
ref
children
child
Data Attribute Details
data-orientation
data-disabled
data-accordion-content
CSS Variable Details
--bits-accordion-content-height
--bits-accordion-content-width