Collapsible
The one disclosure: a header with an action, details, an end slot and a chevron, over an animated panel.
import { Collapsible, CollapsibleHeader, CollapsiblePanel, … } from "@arcadia/ui"export const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
Thinking rows, tool lines, step groups, the work summary, notices and settings sections: every disclosure is this one. The header reads action, details, an optional end slot, then the chevron, which follows the text, appears on hover or focus and turns 90° when open; no space is held for it at rest. The panel's height animates over 150ms while its content fades.
Examples
A settings section
The same anatomy outside the stream: sm for UI text, an icon, and controls in the panel.
rootStyle on the root takes where the disclosure sits and these knobs; its header and panel read them. Tones and sizes only reassign them, and hover reads its own (--collapsible-action-hover), so a colour you set never drops it. Set data-instant on any ancestor to still every transition inside it.
| Knob | Takes | Default | Sets |
|---|---|---|---|
Ref<FontSize> | var(--font-size-lg) | The header's font size. | |
Ref<LineHeight> | var(--line-height-lg) | The header's line height. | |
Ref<Tracking> | var(--tracking-lg) | The header's letter spacing. | |
Ref<Space> | "0px" | var(--space-1) | Gap between the panel's children (and above the first). | |
Paint | var(--icon-3) | The leading glyph. | |
Paint | var(--text-2) | The verb ("Explored"). | |
Paint | var(--text-1) | The verb under the pointer. | |
Paint | var(--text-3) | The object ("4 files, 2 searches"). | |
Paint | var(--text-2) | The object under the pointer. | |
Ref<Space> | "0px" | var(--space-0-5) | How far the header and panel hang outside the text edge: room for the inset focus ring. | |
0 | 1 | 0 | Keep the disclosure chevron visible at rest (0 by default; use 1 for reference navigation). |
Set --collapsible-chevron-rest to 1 when the disclosure should advertise itself before hover, as these reference sections do.
The header is a <button> with aria-expanded, controlling the panel. Closed panels stay in the DOM as hidden="until-found", so find in page reaches collapsed output and opens it. A loading header's shimmer is decorative; announce the work in a live region around the stream.
- EnterSpace
- Toggles the panel
Collapsible
open- boolean
- Controlled open state.
defaultOpen- booleanDefaultfalse
- Initial open state when uncontrolled.
onOpenChange- (open: boolean, details: OpenChangeDetails) => void
- Called when the header toggles it (
details.reason:trigger-press) or a find-in-page match opens it. disabled- booleanDefaultfalse
- Blocks toggling.
tone- "neutral" | "muted"Default"neutral"
- muted: completed process rows (action and details at text-3; hover text-1 / text-2).
size- "sm" | "md"Default"md"
md: conversation text, 15/24.sm: UI text, 13/18, for cards and trays.expandable- booleanDefaulttrue
- false: a static line (no chevron, no toggle), same anatomy.
loading- booleanDefaultfalse
- Live: the action shimmers (Shimmer secondary, tertiary when muted).
instant- boolean
- Skip every height/opacity/rotation transition (hydration, bulk collapse, live → ended).
rootStyle- RootStyle<CollapsibleKnobs>
- Typed StyleX override: where the disclosure sits and its
--collapsible-*knobs. className- string
- A product's hook class for its own rules (being retired, counted by ui.lint's
kit-classname).
Rest props go to <div> props.
actionrequired- ReactNode
- The verb: "Explored", "Thought", "Worked for". Pass a node for custom content.
details- ReactNode
- The object: "4 files, 2 searches", "12s", a path. Ellipsizes first.
end- ReactNode
- Trailing inline content before the chevron, e.g. diff stats "+10 −1".
tone- "neutral" | "muted"
- Overrides the root's tone for this header only.
loading- boolean
- Overrides the root's loading state for this header only.
title- string
- The header's hover hint. Shown as the kit Tooltip beside the row, never the native
titleattribute: a browser tooltip opens under the pointer and lands on the next line, which in a transcript covers the row below. render- RenderProp<{ disabled: boolean }>
- Replace the rendered element, e.g.
render={<a href="/settings" />}, or a function of props and state. disabled- boolean
- Native disabled: no pointer events, no tooltip, skipped by Tab.
focusableWhenDisabled- boolean
- Keep a disabled control focusable and hoverable (
aria-disabled), so a tooltip can say why. nativeButton- boolean
- Button semantics, inferred from what renders: a
<button>is native, an<a>stays a link, anything else getsrole="button"and key handling.falseforces button semantics on whatever renders (a link acting as a button). className- string
- A product's hook class for its own rules (being retired, counted by ui.lint's
kit-classname).
Rest props go to <button> props.
hiddenUntilFound- booleanDefaulttrue
- Closed panels stay in the DOM as
hidden="until-found", so ⌘F finds collapsed output. className- string
- A product's hook class for its own rules (being retired, counted by ui.lint's
kit-classname).
Rest props go to <div> props.