Collapsible
The one disclosure: a header with an action, details, an end slot and a chevron, over an animated panel.
import { Collapsible, CollapsibleHeader, CollapsiblePanel and 1 more } 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 is the kit's one fold, Unfold: it opens over 200ms and closes over 160ms on --ease-enter while its content fades, and the chevron turns with it.
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.
--collapsible-font- Ref<FontSize>Default var(--font-size-lg)
- The header's font size.
--collapsible-lh- Ref<LineHeight>Default var(--line-height-lg)
- The header's line height.
--collapsible-tracking- Ref<Tracking>Default var(--tracking-lg)
- The header's letter spacing.
--collapsible-gap- Ref<Space> | Ref<"--stream-gap"> | "0px"Default var(--space-1)
- Gap between the panel's children (and above the first).
--collapsible-end-gap- Ref<Space>Default var(--space-1)
- Gap between the end slot's items.
--collapsible-icon- PaintDefault var(--icon-3)
- The leading glyph.
--collapsible-action- PaintDefault var(--text-2)
- The verb ("Explored").
--collapsible-action-hover- PaintDefault var(--text-1)
- The verb under the pointer.
--collapsible-details- PaintDefault var(--text-3)
- The object ("4 files, 2 searches").
--collapsible-details-hover- PaintDefault var(--text-2)
- The object under the pointer.
--collapsible-ring-pad- Ref<Space> | "0px"Default var(--space-0-5)
- How far the header and panel hang outside the text edge: room for the inset focus ring.
--collapsible-chevron-rest- 0 | 1Default 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- booleanDefault false
- 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- booleanDefault false
- 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- booleanDefault true
- false: a static line (no chevron, no toggle), same anatomy.
loading- booleanDefault false
- 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. Gives way first: its end fades.
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- booleanDefault true
- Closed panels stay in the DOM as
hidden="until-found", so ⌘F finds collapsed output (and opens it).false: the content unmounts once the fold has closed. bare- booleanDefault false
- Open, no header yet: no gap above, no clip. It only closes at once.
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.