Skip to content
Navigation

Accordion

Vertically stacked sections that expand one (or several) at a time. For a single independent section use Collapsible; for switching between peer views use Tabs.

Added 14 October 2026

Accordion, Collapsible or Tabs?

  • Accordion — a list of related sections where reading one at a time is fine (FAQs, settings groups, order line details). Items share one container and a divider rhythm.
  • Collapsible — a single independent section with its own trigger; no sibling coordination.
  • Tabs — peer views of the same subject where switching replaces the content; nothing stacks.

Single vs multiple open

Single-open is the default in both frameworks (React Base UI multiple defaults to false; Angular brain type defaults to 'single'). Keep it — it preserves the reader’s place and keeps the page height stable. Opt into multiple only when users genuinely compare section contents side by side (React multiple, Angular type="multiple").

Composition

React bakes the chevron pair into AccordionTrigger — pass only the label:

<Accordion defaultValue={["formats"]}>
  <AccordionItem value="formats">
    <AccordionTrigger>Which ad formats are supported?</AccordionTrigger>
    <AccordionContent>
      <p>Billboard, sitebar and mobile banner…</p>
    </AccordionContent>
  </AccordionItem>
</Accordion>

Open state is controlled on the root in React (defaultValue, value, onValueChange — arrays of item values) but per item in Angular: isOpened for default-open, [(isOpened)] / (isOpenedChange) for controlled use (brain has no root-level value API). Both frameworks render the trigger inside an h3 by default; Angular consumers may pick another heading level (brain accepts h1–h6), React’s is fixed by Base UI.

Angular composes the chevron explicitly (ng-icon[hlmAccordionIcon], rotates when open), and the trigger must be a <button> wrapped in a heading — brain enforces this at runtime:

<div hlmAccordion>
  <div hlmAccordionItem>
    <h3 hlmAccordionHeader>
      <button hlmAccordionTrigger>
        Which ad formats are supported?
        <ng-icon hlmAccordionIcon name="lucideChevronDown" aria-hidden="true" />
      </button>
    </h3>
    <hlm-accordion-content>
      <p>Billboard, sitebar and mobile banner…</p>
    </hlm-accordion-content>
  </div>
</div>

Keyboard

Tab/Shift-Tab and Enter/Space work in both frameworks. Arrow-key travel between triggers (plus Home/End) is Angular-only — brain ships a roving-focus key manager, while Base UI 1.6 dropped arrow-key navigation. Upstream behavior on both sides; don’t hand-roll arrows in React. [a11y]

Rules

  • Trigger content is a label, not a control surface: no buttons, links or inputs inside the trigger — they nest inside a <button> and break semantics. [a11y]
  • Put borders/padding on elements inside the content, not on the content part itself — the panel is height-clamped and clipped while animating, so its own border-box would stay visible as it collapses. In React, className on AccordionContent already targets the inner padded div and is safe; in Angular, class on <hlm-accordion-content> lands on the clipped host — style an element inside instead.
  • Disable per item (disabled on AccordionItem / hlmAccordionItem), and never let disabled be the only explanation — say why nearby. Note the frameworks differ: a disabled React trigger stays focusable and is announced (aria-disabled), a disabled Angular trigger leaves the Tab order entirely (native disabled from brain). [a11y]
  • Content is prose-styled (text-sm, link underlines, paragraph spacing) — don’t rebuild those recipes per usage.