Accordion
A stack of headers that each reveal a section of content — most often an FAQ.
Built on Base UI Accordion. Single-open by default; one section expands as
another collapses.
Import
import { Accordion } from "@photon-ai/pho-ui/components/accordion";
Basic
Each Accordion.Item carries a value. Set the initially-open items with
defaultValue (an array), or drive the open set with value / onValueChange.
The chevron in the trigger rotates when its panel is open.
import { IconChevronDown } from "@tabler/icons-react";
<Accordion.Root defaultValue={["what"]}>
<Accordion.Item value="what">
<Accordion.Header>
<Accordion.Trigger>
What is Pho?
<IconChevronDown />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>
<div className="pb-4">…</div>
</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>;
Multiple open
By default one panel opens as another closes. Set multiple to let several
stay open at once — defaultValue (and value) then hold every open item.
<Accordion.Root multiple defaultValue={["what", "base-ui"]}>
{/* items */}
</Accordion.Root>
Anatomy
- Accordion.Root — owns the open set;
multiple,value/defaultValue - Accordion.Item — one section, identified by
value - Accordion.Header → Accordion.Trigger — the
<h3>heading and its toggle button - Accordion.Panel — animated content region
Props
Set on Accordion.Root:
- value —
Value[]— controlled array of open item values - defaultValue —
Value[]— uncontrolled initial open items - onValueChange —
(value, eventDetails) => void— fires when the open set changes - multiple —
boolean(defaultfalse) — allow several panels open at once - disabled —
boolean(defaultfalse) — disable every item - keepMounted —
boolean(defaultfalse) — keep collapsed panels in the DOM - hiddenUntilFound —
boolean(defaultfalse) — keep panels findable by the browser’s in-page search (implieskeepMounted)
Set on Accordion.Item:
- value —
any— unique identifier for the item; auto-generated if omitted - disabled —
boolean(defaultfalse) — disable this item - onOpenChange —
(open, eventDetails) => void— fires when this item opens or closes
Accordion.Trigger renders a native <button>; Accordion.Panel accepts
keepMounted and hiddenUntilFound to override the Root defaults per item.
Accessibility
Each Accordion.Header renders an <h3> that wraps the Accordion.Trigger
<button>. The trigger carries aria-expanded and aria-controls pointing at
its panel; the Accordion.Panel is a role="region" labelled by its trigger
via aria-labelledby. Keep the surrounding heading levels sensible — if <h3>
is wrong for the page, re-render the header as another heading with render.
Keyboard:
- Tab / Shift + Tab — move between triggers
- Enter / Space — toggle the focused trigger
Base UI follows the updated APG guidance that removed roving focus, so triggers
sit in the normal tab order — there is no arrow-key navigation between headers.
A collapsed panel is removed from the accessibility tree (and the DOM, unless
keepMounted or hiddenUntilFound is set).
Best practices
- Reach for an accordion when content is long and scannable by heading (FAQs, settings groups) and users only need one section at a time.
- Keep
multipleoff unless sections are genuinely independent — the single-open default keeps the page short. - Pad panel content with an inner wrapper (
pb-4) rather than on the panel itself, so the height animation stays smooth. - Write triggers as questions or noun phrases that stand alone in a screen reader’s heading list; the panel should answer the header.