Components / disclosure

Accordion

Radix is permitted here — Accordion is on the closed list of six.

Rendered from the package · open on its own

Summary

A stack of disclosures at one heading level — sections of a long form, a FAQ, an inspector.

APG patternAccordion
Built onRadix
Statusstable
Since0.1.0
<Accordion headingLevel={3} type="single" items={[{ id: 'a', summary: 'Intro', content: … }]} />

Anatomy

Each trigger sits inside a real heading element, which is how a screen-reader user skims the set.

PartElementRequired
content<radixaccordion.content>yes
description<span>no
heading<heading>yes
indicator<svg>yes
item<radixaccordion.item>yes
label<span>yes
panel<div>yes
trigger<radixaccordion.trigger>yes

Properties

PropertyValuesDefaultWhat it does
itemsAccordionItem[]requiredThe sections, in display order. Each needs a stable id — it is what value refers to.
typesingle · multiplesinglesingle shows one panel at a time; multiple allows any number.
collapsiblebooleantrueOnly with type="single" — lets the open panel be closed again.
headingLevel1 | 2 | 3 | 4 | 5 | 63The heading level wrapping each trigger. Pick the one that fits the page's outline; a wrong level breaks heading navigation for screen-reader users.
valuestring | string[]noneThe open section, or sections when type="multiple". Passing it makes the accordion controlled.
defaultValuestring | string[]noneWhich section starts open when the accordion keeps its own state.
onValueChange(value: string | string[]) => voidnoneCalled with the new open section, or the array of them under type="multiple".

States

StateExpressed as
Focused:focus-visible
Hover:hover
Open or closeddata-state

Behavior

single closes the open panel when another opens. multiple leaves them.

Accessibility

Set headingLevel deliberately. The right level depends on what surrounds the accordion, so the component will not guess it — a wrong level breaks the document outline, which is the thing a screen-reader user navigates by.

aria-expanded and aria-controls on each trigger, from Radix.

The heading carries no font of its own — it inherits, because the trigger inside it is the styled thing. A heading that looks like a heading inside an accordion competes with the page's real ones.

Keyboard

KeyResult
Enter / SpaceToggles the panel
Arrow Up / Arrow DownMove between triggers
Home / EndJump to the first or last trigger

Roles, states and properties

AttributeSet by
aria-controlsset at render
aria-describedbyset at render
aria-hiddenalways true
aria-labelledbyset at render

Focus

Each trigger is a tab stop; the panel's contents follow in document order. Radix supplies the arrow behavior.

Screen reader

Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.

Contrast and target size

npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.

It sets no target size of its own; anything interactive inside it brings its own.

Tested

  • test/apg/partials-sweep.test.jsx
  • test/browser/upstream-natives.spec.mjs

Known gaps

_No upstream requirement is recorded as not applicable._

Appearance

TokenUsed for
--se-accentcolor
--se-borderborder-block-end, border-block-start
--se-border-widthborder-block-end, border-block-start
--se-control-hmin-block-size
--se-duration-fastanimation, transition
--se-duration-instanttransition
--se-duration-mediumanimation
--se-ease-standardanimation, transition
--se-focus-coloroutline
--se-focus-widthoutline, outline-offset
--se-icon-smblock-size, inline-size
--se-leading-bodyline-height
--se-radius-smborder-radius
--se-space-1row-gap
--se-space-3column-gap, padding-block
--se-space-4padding-block-end
--se-textcolor
--se-text-mutedcolor
--se-text-smfont-size
--se-text-xsfont-size
--se-weight-regularfont-weight

Motion

Height animates over --se-duration-fast.

Content

Each summary names its section as a label, not a question — unless the accordion is a FAQ, where a question is exactly right.

Usage

type="single" is the default and is usually right. Use multiple only when readers genuinely need two panels open to compare them; otherwise it lets the page grow without limit.

An accordion is a set; a disclosure is one thing. The difference is not size. Every trigger here sits inside a heading at one shared level, so a screen-reader user can skim the set by heading, and type="single" can enforce that only one panel is open at a time. A Collapsible has none of that — it knows about no other collapsible on the page — so stacking three of them produces something that looks like this and behaves like three unrelated toggles.

When not to use it. For one section, use Collapsible. If the sections are alternatives and only one is ever relevant, use Tabs.

Notes

Radix is permitted here — Accordion is on the closed list of six.