Collapsible
A single show/hide disclosure that expands and collapses a panel of content.
Show codeHide code
Installation
import { Collapsible } from "@makeplane/propel/components/collapsible";Usage
Pass trigger for the header label and children for the panel body. Pass a public <Icon> into
icon for the leading 16px glyph (Figma’s resting art includes it). The rotating chevron is on by
default (indicator defaults to true). Drive open state with defaultOpen (uncontrolled) or
open + onOpenChange (controlled). Pass keepMounted to leave the panel in the DOM while
closed, or hiddenUntilFound so closed content also stays findable via the browser’s
find-in-page — hiddenUntilFound alone already keeps the panel mounted, so the two are not meant
to be combined (Base UI Panel props).
import { Collapsible } from "@makeplane/propel/components/collapsible";
import { Icon } from "@makeplane/propel/components/icon";
import { Activity } from "lucide-react";
export default function BasicDemo() {
return (
<Collapsible
trigger="Collapsible header"
icon={<Icon icon={Activity} tint="secondary" size="inherit" />}
>
Content that expands and collapses with this section.
</Collapsible>
);
}Anatomy
import { Collapsible as BaseCollapsible } from "@base-ui/react/collapsible";
import {
Collapsible,
CollapsibleTrigger,
CollapsibleTriggerTitle,
CollapsiblePanel,
CollapsiblePanelContent,
} from "@makeplane/propel/components/collapsible";
// Styled Root frame (`w-full`) — separate from the ready-made `Collapsible` above (name collision).
import { Collapsible as CollapsibleFrame } from "@makeplane/propel/elements/collapsible";
// Ready-made — grafts Base UI Root → CollapsibleFrame for you
<Collapsible trigger="…" icon={…}>
…
</Collapsible>
// Atomic parts — graft Base UI yourself
<BaseCollapsible.Root render={<CollapsibleFrame />}>
<BaseCollapsible.Trigger render={<CollapsibleTrigger />}>
{icon}
<CollapsibleTriggerTitle>…</CollapsibleTriggerTitle>
{/* Rotating chevron — the shared caret is internal; supply your own */}
</BaseCollapsible.Trigger>
<BaseCollapsible.Panel render={<CollapsiblePanel />}>
<CollapsiblePanelContent>…</CollapsiblePanelContent>
</BaseCollapsible.Panel>
</BaseCollapsible.Root>The Root frame is full width so centered hosts don’t shrink-wrap when closed. The trigger packs
[icon?][title][chevron] as a left-aligned cluster. Long titles wrap instead of staying
single-line (Figma’s short sample uses nowrap). Panel padding and body type live on
CollapsiblePanelContent (px-3 pt-1.5 pb-3 + body-xs secondary) so the height animation on
CollapsiblePanel measures cleanly and List sections (children directly in the panel) do not
inherit Collapsible prose tokens. The rotating chevron itself is an internal shared primitive (not
exported) — an atomic assembly supplies its own, e.g. a lucide-react ChevronDown rotated off
data-panel-open.
Examples
Without icon
Omit icon when the row should be label + chevron only.
Show codeHide code
Default open
Show codeHide code
Controlled
Pass open and onOpenChange to drive the disclosure from your own state instead of defaultOpen.
Show codeHide code
Without indicator
Show codeHide code
Disabled
Pass Root disabled to dim the trigger and block open/close.
Show codeHide code
Keep mounted
Pass keepMounted to leave the panel in the DOM while closed. hiddenUntilFound does the same
(plus browser find-in-page) — pass one or the other, not both.
Show codeHide code
Accessibility
- The trigger is a native
<button>with Base UI’saria-expanded/aria-controls. - The chevron is decorative (
aria-hidden); the accessible name comes from the trigger label. - Disabled state dims the row via Root
disabled(data-disabled/aria-disabled) and swaps the cursor; the trigger stays focusable but will not toggle. - Focus-visible draws the shared accent ring on the trigger.
API Reference
Collapsible
The ready-made collapsible: a single show/hide disclosure that wires the trigger and panel for the 90% case. Pass `trigger` for the toggle button label and `children` for the body; optional `icon` for a leading glyph. Forward `defaultOpen` (uncontrolled) or `open` + `onOpenChange` (controlled) to drive it. Set `indicator={false}` to omit the rotating chevron from the trigger.