Collapsible
Shows or hides a single region of content with a smooth height animation. Built
on Base UI Collapsible. For several sections where one expands at a time,
reach for Accordion.
Import
import { Collapsible } from "@photon-ai/pho-ui/components/collapsible";
Basic
Collapsible.Trigger toggles Collapsible.Panel. Any <svg> in the trigger
rotates when open. Left uncontrolled, the panel starts closed unless you pass
defaultOpen.
Deployed photon-web to production · 2h ago
Merged #482 “Fix avatar fallback” · 5h ago
Opened #486 “Add Slider component” · 6h ago
import { IconChevronDown } from "@tabler/icons-react";
<Collapsible.Root defaultOpen>
<Collapsible.Trigger>
Recent activity
<IconChevronDown />
</Collapsible.Trigger>
<Collapsible.Panel>
<div className="pt-3">…</div>
</Collapsible.Panel>
</Collapsible.Root>;
Controlled
Drive the open state yourself with open + onOpenChange when another control
needs to read or set it. Omit both to stay uncontrolled and let the trigger
manage it.
Panel is closed.
const [open, setOpen] = useState(false);
<Collapsible.Root open={open} onOpenChange={setOpen}>
<Collapsible.Trigger>
Environment variables
<IconChevronDown />
</Collapsible.Trigger>
<Collapsible.Panel>
<div className="pt-3">…</div>
</Collapsible.Panel>
</Collapsible.Root>;
Anatomy
- Collapsible.Root — owns the open state; renders a
<div> - Collapsible.Trigger — the
<button>that toggles the panel; getsdata-panel-openwhen open - Collapsible.Panel — animates
heightvia--collapsible-panel-height
Props
- Collapsible.Root
- open —
boolean— controlled open state; pair withonOpenChange - defaultOpen —
boolean(defaultfalse) — initial open state when uncontrolled - onOpenChange —
(open: boolean, details) => void— fires when the panel opens or closes - disabled —
boolean(defaultfalse) — ignore user interaction
- open —
- Collapsible.Trigger — a native
<button>; accepts all<button>attributes - Collapsible.Panel
- keepMounted —
boolean(defaultfalse) — keep content in the DOM while closed - hiddenUntilFound —
boolean(defaultfalse) — hide withhidden="until-found"so browser find-in-page can reveal it (implieskeepMounted)
- keepMounted —
All three parts also forward className and their native element attributes.
Accessibility
- Semantics —
Collapsible.Triggeris a native<button>witharia-expandedreflecting the open state andaria-controlspointing at the panel while it’s open. A closed panel is removed from the DOM (and the accessibility tree), unless you opt intokeepMounted/hiddenUntilFound— then it stays mounted and is hidden with thehiddenattribute instead. - Focus — focus stays on the trigger through the toggle; it never jumps into
the panel. The trigger shows the brand ring on
focus-visibleonly. - Find in page — set
hiddenUntilFoundon the panel so the browser’s in-page search can match hidden text and auto-expand the panel to reveal it. - Labelling — give the trigger a clear text label naming what it reveals; the rotating chevron is decorative and carries no state on its own.
Keyboard:
Tab— move focus to the triggerEnter/Space— toggle the panel
Best practices
- Use a collapsible to tuck away secondary detail (activity, advanced options) without leaving the page. Don’t hide anything essential to the main task.
- Keep the trigger label descriptive of what’s inside, and let the chevron show state — don’t rename the trigger between open and closed.
- Stay uncontrolled (
defaultOpen) unless another control needs the state; reach foropen/onOpenChangeonly when you truly need to drive it. - Pad panel content with an inner wrapper (
pt-3) rather than the panel itself, so the height animation stays smooth. - When several collapsibles are mutually exclusive, switch to
Accordion.