Accordion

A set of collapsible panels for FAQs and progressive disclosure, accessible by default. Base UI handles aria-expanded, heading semantics and keyboard navigation.

Default

Only one panel stays open at a time. Pass an array to defaultValue to set the initially expanded item.

Yes. We ship to 140 countries as long as your customs form doesn't say "definitely not a rocket".

import { Accordion } from "@/components/ui/accordion"

export default function Example() {
  return (
    <div className="flex w-full justify-center">
      <Accordion.Root defaultValue={["shipping"]} className="w-full max-w-13">
        <Accordion.Item value="shipping">
          <Accordion.Trigger>Do you ship internationally?</Accordion.Trigger>
          <Accordion.Panel>
            Yes. We ship to 140 countries as long as your customs form doesn't
            say "definitely not a rocket".
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="returns">
          <Accordion.Trigger>What's your return policy?</Accordion.Trigger>
          <Accordion.Panel>
            30 days, no questions asked. Slightly burnt rocket fuel residue is
            still considered "like new".
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="warranty">
          <Accordion.Trigger>Is there a warranty?</Accordion.Trigger>
          <Accordion.Panel>
            Lifetime coverage against spontaneous combustion. Atmospheric
            re-entry scratches not included.
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="support">
          <Accordion.Trigger>How do I reach support?</Accordion.Trigger>
          <Accordion.Panel>
            Ping us anytime. Response times range from "instant" to "after we
            land" depending on orbital position.
          </Accordion.Panel>
        </Accordion.Item>
      </Accordion.Root>
    </div>
  )
}

Anatomy

Each Accordion.Item wraps a trigger and panel. Set value on every item when you control which panels are open.

import { Accordion } from "@/components/ui/accordion"

<Accordion.Root>
  <Accordion.Item>
    <Accordion.Trigger />
    <Accordion.Panel />
  </Accordion.Item>
</Accordion.Root>

Multiple

Pass multiple so several panels can stay open together. Use this when readers compare answers across items instead of reading them one at a time.

Yes. We ship to 140 countries as long as your customs form doesn't say "definitely not a rocket".

import { Accordion } from "@/components/ui/accordion"

export default function Example() {
  return (
    <div className="flex w-full justify-center">
      <Accordion.Root multiple defaultValue={["shipping"]} className="w-full max-w-13">
        <Accordion.Item value="shipping">
          <Accordion.Trigger>Do you ship internationally?</Accordion.Trigger>
          <Accordion.Panel>
            Yes. We ship to 140 countries as long as your customs form doesn't
            say "definitely not a rocket".
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="returns">
          <Accordion.Trigger>What's your return policy?</Accordion.Trigger>
          <Accordion.Panel>
            30 days, no questions asked. Slightly burnt rocket fuel residue is
            still considered "like new".
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="warranty">
          <Accordion.Trigger>Is there a warranty?</Accordion.Trigger>
          <Accordion.Panel>
            Lifetime coverage against spontaneous combustion. Atmospheric
            re-entry scratches not included.
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="support">
          <Accordion.Trigger>How do I reach support?</Accordion.Trigger>
          <Accordion.Panel>
            Ping us anytime. Response times range from "instant" to "after we
            land" depending on orbital position.
          </Accordion.Panel>
        </Accordion.Item>
      </Accordion.Root>
    </div>
  )
}

Disabled

Set disabled on an item to block expansion while keeping the label visible. Use it for sections that are unavailable or require permission.

Yes. We ship to 140 countries as long as your customs form doesn't say "definitely not a rocket".

import { Accordion } from "@/components/ui/accordion"

export default function Example() {
  return (
    <div className="flex w-full justify-center">
      <Accordion.Root defaultValue={["shipping"]} className="w-full max-w-13">
        <Accordion.Item value="shipping">
          <Accordion.Trigger>Do you ship internationally?</Accordion.Trigger>
          <Accordion.Panel>
            Yes. We ship to 140 countries as long as your customs form doesn't
            say "definitely not a rocket".
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="classified" disabled>
          <Accordion.Trigger>Where is the secret launch pad?</Accordion.Trigger>
          <Accordion.Panel>
            Nice try. This one's classified until you clear the background check.
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="support">
          <Accordion.Trigger>How do I reach support?</Accordion.Trigger>
          <Accordion.Panel>
            Ping us anytime. Response times range from "instant" to "after we
            land" depending on orbital position.
          </Accordion.Panel>
        </Accordion.Item>
      </Accordion.Root>
    </div>
  )
}

Borderless

Pass bordered={false} on Accordion.Root to drop dividers between items when the accordion already sits inside a card or list with its own separation.

Yes. We ship to 140 countries as long as your customs form doesn't say "definitely not a rocket".

import { Accordion } from "@/components/ui/accordion"

export default function Example() {
  return (
    <div className="flex w-full justify-center">
      <Accordion.Root bordered={false} defaultValue={["shipping"]} className="w-full max-w-13">
        <Accordion.Item value="shipping">
          <Accordion.Trigger>Do you ship internationally?</Accordion.Trigger>
          <Accordion.Panel>
            Yes. We ship to 140 countries as long as your customs form doesn't
            say "definitely not a rocket".
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="returns">
          <Accordion.Trigger>What's your return policy?</Accordion.Trigger>
          <Accordion.Panel>
            30 days, no questions asked. Slightly burnt rocket fuel residue is
            still considered "like new".
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="warranty">
          <Accordion.Trigger>Is there a warranty?</Accordion.Trigger>
          <Accordion.Panel>
            Lifetime coverage against spontaneous combustion. Atmospheric
            re-entry scratches not included.
          </Accordion.Panel>
        </Accordion.Item>
        <Accordion.Item value="support">
          <Accordion.Trigger>How do I reach support?</Accordion.Trigger>
          <Accordion.Panel>
            Ping us anytime. Response times range from "instant" to "after we
            land" depending on orbital position.
          </Accordion.Panel>
        </Accordion.Item>
      </Accordion.Root>
    </div>
  )
}

Props

Accordion.Root

PropTypeDefault
borderedbooleantrue
multiplebooleanfalse
valuestring[]
defaultValuestring[]
onValueChange(value: string[], eventDetails) => void
disabledbooleanfalse
hiddenUntilFoundbooleanfalse
keepMountedbooleanfalse

Accordion.Item

PropTypeDefault
valuestringauto-generated
onOpenChange(open: boolean, eventDetails) => void
disabledbooleanfalse

Accordion.Trigger

PropTypeDefault
classNamestring
nativeButtonbooleantrue

Accordion.Panel

PropTypeDefault
keepMountedbooleanfalse
hiddenUntilFoundbooleanfalse

This covers the parts used above. See the Base UI Accordion docs for render, event details, CSS variables and data attributes.