opsinjs
ComponentsNavigation

Accordion

Sections that expand and collapse, one or several at a time, built on Base UI Accordion.

Preview

Theme
Density
Text
Status
accordion · base base · style base-lyraOpen under the product theme

Installation

pnpm dlx shadcn@latest add @opsinjs/accordion

The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.

Usage

import { Accordion } from "@/components/ui/accordion"
<Accordion
  items={[
    { value: "hours", title: "When can I reach the team?", content: "Weekday mornings." },
    { value: "records", title: "Where are my notes kept?", content: "On your device." },
  ]}
/>

When to use it

Use it when

  • An index of related sections a reader scans first and opens one at a time, such as questions and answers.
  • Detail a reader wants on demand but not on arrival, such as small print under a summary.
  • Several independent groups of settings on one small screen, opened together with multiple.

Do not use it when

  • A safety message, an urgent instruction or anything a reader must not miss. A collapsed panel may never be opened.Use callout instead.
  • Content that must always be visible, not behind a header a reader has to press.Use card instead.
  • Choosing a header should swap the whole view for another in the same space, not reveal detail in place.Use tabs instead.

Anatomy

  1. AccordionBase UI's Accordion.Root: one card surface with a hairline that manages open state. Neutral chrome, neither colour axis.Controlled by items
  2. Accordion.ItemOne section, a header with its panel. A top hairline divides it from the section above.Controlled by items
  3. Accordion.HeaderThe section's heading, rendered by Base UI as an h3 so it sits in the document outline.
  4. Accordion.TriggerThe full-width button that toggles the section, with aria-expanded, aria-controls, a 44pt target floor, the title and chevron.Controlled by value
  5. Accordion.PanelThe revealed content, unmounted while closed, so a shut panel costs nothing and leaves the tab order.Controlled by content
  • Accordion 1
    • Accordion.Item 1..n
      • Accordion.Header 1
        • Accordion.Trigger 1
      • Accordion.Panel 1

Examples

A frequently asked list

Three headers form an index; the reader opens one. Fictional copy.

Theme
Density
Text
Status
accordion-frequently-asked · base base · style base-lyraOpen under the product theme

One section open at a time

With multiple off (the default), opening one section closes the last.

Theme
Density
Text
Status
accordion-one-open-at-a-time · base base · style base-lyraOpen under the product theme

Content guidelines

Write headers as a scannable index: a question, a noun phrase or a setting name, in sentence case, without terminal punctuation. Never fold a warning or a required step into a panel that starts closed. It belongs in a Callout that stays open.

Do

"When can I reach the team?" The reader's own question, short enough to scan against its siblings.

Don’t

A "More" panel holding "Call your clinician if this reading keeps rising." An instruction the reader may never open.

Accessibility

Audited against WCAG 2.2 AA in a source pass and a rendered pass. It is author-run, not independent, and clinical review is pending. The rendered pass (axe, target size, focus visible, 320px reflow) was clean.

One defect was fixed. The trigger's focus ring was drawn outside the trigger, where the root's overflow-hidden clipped it (2.4.7). It now renders inside, whole on every straight edge, with a small notch still clipped at the first and last sections' outer corners, a known residual.

  • Base UI renders each header as an h3 with a button, wires aria-expanded and aria-controls between trigger and panel, and hides the chevron with aria-hidden, so a reader relies on aria-expanded, not the glyph.
  • The chevron's turn uses --opsin-duration-fast, collapsed under prefers-reduced-motion. A closed panel is unmounted, leaving the tab order.

Still unmeasured. The rendered pass does not compute contrast, so every pair against the card surface is unmeasured, not known-failing. That no must-see content may sit in a closed panel stays a docs contract the wrapper cannot enforce.

KeyActionNotes
TabMoves focus to the next headerAn ordinary tab stop. A disabled header keeps its stop.
Shift+TabMoves focus to the previous headerThe same in reverse.
Enter, SpaceOpens or closes the focused sectionA native button. With multiple off, the open section closes.
Arrow keysNot boundBase UI follows the updated APG guidance, which dropped roving focus.
PairThemeAPCA LcWCAG 2.2Floor
body text on the pagelight101.617.18:1Pass
body text on the pagedark-100.518.00:1Pass
body text on a cardlight104.717.96:1Pass
body text on a carddark-99.616.32:1Pass
body text on the muted groundlight98.116.32:1Pass
body text on the muted grounddark-97.513.77:1Pass
secondary text on the pagelight83.97.42:1Pass
secondary text on the pagedark-80.713.44:1Pass
secondary text on a cardlight87.07.76:1Pass
secondary text on a carddark-79.812.19:1Pass
secondary text on the muted groundlight80.47.05:1Pass
secondary text on the muted grounddark-77.810.28:1Pass
a hairline boundary on the pagelight19.31.41:1Below floor
a hairline boundary on the pagedark-8.21.90:1Below floor
a hairline boundary on a cardlight22.41.47:1Below floor
a hairline boundary on a carddark-7.31.72:1Below floor
a hairline boundary on the muted groundlight15.81.34:1Below floor
a hairline boundary on the muted grounddark0.01.45:1Below floor
a placeholder boundary on the pagelight69.84.52:1Pass
a placeholder boundary on the pagedark-51.07.62:1Pass
a placeholder boundary on a cardlight72.84.72:1Pass
a placeholder boundary on a carddark-50.16.91:1Pass
a placeholder boundary on the muted groundlight66.24.29:1Pass
a placeholder boundary on the muted grounddark-48.15.83:1Pass
a placeholder fill on the pagelight8.31.18:1Below floor
a placeholder fill on the pagedark-8.21.90:1Below floor
a placeholder fill on a cardlight11.41.23:1Below floor
a placeholder fill on a carddark-7.31.72:1Below floor
a placeholder fill on the muted groundlight0.01.12:1Below floor
a placeholder fill on the muted grounddark0.01.45:1Below floor
a placeholder boundary on its own filllight58.93.84:1Pass
a placeholder boundary on its own filldark-41.54.01:1Below floor
the card hairline on the pagelight69.84.52:1Pass
the card hairline on the pagedark-28.74.19:1Below floor
the card hairline on a cardlight72.84.72:1Pass
the card hairline on a carddark-27.93.80:1Below floor

These are the measured token pairs this component draws colour from, not a measurement of the component itself.

Data attributes

AttributeConditionValue
data-slotOn the root and each partaccordion, accordion-item, accordion-header, accordion-trigger, accordion-title, accordion-panel
data-openOn the item and the panelPresent while expanded
data-panel-openOn the triggerPresent while its section is open
data-disabledOn the item and its triggerPresent while disabled

API reference

Prop

Type

Generated from AccordionProps in registry/bases/base/accordion.tsx.

multiple defaults to false. value without onValueChange fixes the open sections as given. An empty items array renders nothing and warns in development, as does a duplicated value.

  • Tabs swap the whole panel for another, where an accordion reveals detail in place.
  • Card holds content that stays open, with no header to press.
  • Callout sets apart what a reader cannot miss. A warning never goes in a collapsed panel.

On this page