sc1m/design
ComponentsAccordion

Accordion

A vertically stacked set of headers that each reveal a section of content. Use it to compress secondary detail into a scannable list — not to hide anything a person needs to complete a task.

StableBase UIWAI-ARIA Disclosure

Opening an item detaches it from the stack: it becomes its own card with air around it and a lift above the rest, so the section you are reading is visibly separate from the ones you are not. Closed items keep a shared border and collapse back together on close.

Live preview

Installation

pnpm dlx shadcn@latest add @sc1m/accordion

This also merges the token layer and installs src/lib/cn.ts plus @base-ui/react. For the manual route — prerequisites, components.json, and app-level setup — see Installation.

Usage

import {
  Accordion,
  AccordionItem,
  AccordionTrigger,
  AccordionPanel,
} from "@/components/ui/accordion";
<Accordion>
  <AccordionItem value="access">
    <AccordionTrigger>Is it accessible?</AccordionTrigger>
    <AccordionPanel>
      Yes. Base UI ships the WAI-ARIA disclosure pattern.
    </AccordionPanel>
  </AccordionItem>
</Accordion>

Composition

Every part is a separate export. Accordion owns state; the rest are presentational and must appear in this order.

Accordion├── AccordionItem│   ├── AccordionTrigger│   └── AccordionPanel└── AccordionItem    ├── AccordionTrigger    └── AccordionPanel

Multiple

Set multiple to let several items stay open at once. Open state is an array either way — defaultValue takes one even in single mode.

Note that detached expansion was designed around a single open item: two cards detaching at once loses the sense that they belong to one group. Prefer the default single-open behaviour unless the sections are genuinely independent.

Choose how updates reach you — email digests, push, or nothing at all. Changes apply to every workspace you belong to.

<Accordion multiple defaultValue={["notifications"]}>
  <AccordionItem value="notifications">
    <AccordionTrigger>Notification settings</AccordionTrigger>
    <AccordionPanel>Choose how updates reach you.</AccordionPanel>
  </AccordionItem>
  <AccordionItem value="privacy">…</AccordionItem>
  <AccordionItem value="billing">…</AccordionItem>
</Accordion>

Disabled

Put disabled on an AccordionItem to lock a single row. It stays visible and stops responding; put disabled on the root to lock all of them.

<Accordion>
  <AccordionItem value="history">…</AccordionItem>

  {/* locked row — visible, not operable */}
  <AccordionItem value="premium" disabled>…</AccordionItem>

  <AccordionItem value="email">…</AccordionItem>
</Accordion>

API reference

Each part forwards every prop of its Base UI counterpart. The table lists the ones you reach for; see Base UI's accordion reference for the rest.

Accordion

PropTypeDefaultDescription
multiplebooleanfalseWhether more than one item can be open.
defaultValueValue[]Initially open items, uncontrolled.
valueValue[]Controlled open items. Pair with onValueChange.
onValueChange(value, details) => voidFires after each expand or collapse.
disabledbooleanfalseLocks every item in the group.
keepMountedbooleanfalseKeeps closed panels in the DOM.
hiddenUntilFoundbooleanfalseLets browser find-in-page open a panel. Overrides keepMounted.

AccordionItem

PropTypeDefaultDescription
valueanyautoIdentifies the item in open state. Generated if omitted.
disabledbooleanfalseLocks this row only.
onOpenChange(open, details) => voidFires when this item alone toggles.

Accessibility

Base UI implements the ARIA disclosure pattern. Each trigger is a real button inside a heading, labelled by its own text and pointing at its panel with aria-controls.

KeyBehaviour
Space / EnterToggles the focused item.
TabMoves to the next trigger, then into an open panel's content.

Arrow keys do not move between triggers. Base UI dropped roving focus following the APG guidance update — every trigger is in the normal tab order instead, so Tab is the only traversal key.

The focus ring is a 2px outline drawn inside the trigger, and the chevron is aria-hidden.

Design tokens

The component reads semantic tokens only — no raw colour, radius, or duration appears in its source. Override the token, not the component.

TokenResolves toApplied to
--surface-raisedbg-surface-raisedItem background
--lineborder-lineOuter border and item dividers
--surfacehover:bg-surfaceTrigger hover
--fgtext-fgTrigger label
--fg-mutedtext-fg-mutedPanel body
--fg-subtletext-fg-subtleChevron
--ringoutline-ringFocus ring
--shadow-raisedshadow-raisedLift on the open item
--brand-radius-lgrounded-lgRoot corners
--acc-expand250msPanel open
--acc-collapse250msPanel close
--acc-chevron250msChevron flip
--acc-easecubic-bezier(.22,1,.36,1)All three of the above

Source

src/components/ui/accordion/accordion.tsx — no next/* imports, so it drops into any React 19 + Tailwind v4 app.