Collapsible
An interactive component that expands and collapses content with smooth animations.
Overview
The Collapsible component provides an interactive way to show and hide content with smooth CSS transitions. Built on Base UI primitives, it supports both controlled and uncontrolled states, making it perfect for creating expandable sections, details panels, and progressive disclosure interfaces.
Usage
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "@tilt-legal/cubitt-components/primitives";<Collapsible>
<CollapsiblePanel>Your collapsible content goes here</CollapsiblePanel>
<CollapsibleTrigger />
</Collapsible>Examples
Card with Collapsible
Combine collapsible with cards to create expandable information panels.
"use client";
import { cn } from "@tilt-legal/cubitt-components/utilities";
import {
Button,
Badge,
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
Card,
CardContent,
CardHeader,
CardHeading,
CardToolbar,
} from "@tilt-legal/cubitt-components/primitives";
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "@tilt-legal/cubitt-components/primitives";
import {
ChevronDown,
ChevronUp,
ArrowDown,
ArrowUp,
} from "@tilt-legal/cubitt-icons/ui/outline";
export default function Component() {
const [isOpen,
setIsOpen] = React.useState(false);
interface IStatItem {
label: string;
value: number;
change: string;
changeType: "increase" | "decrease";
}
const stats: IStatItem[] = [
{
label: "Added to Cart",
value: 3842,
change: "+1.8%",
changeType: "increase",
},
{
label: "Reached Checkout",
value: 1256,
change: "-1.2%",
changeType: "decrease",
},
{
label: "Purchased",
value: 649,
change: "+2.4%",
changeType: "increase",
},
];
return (
<Card className="w-full max-w-[350px]">
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CardHeader
className={`h-auto py-4 transition-[border-radius,
margin] ${isOpen ? "rounded-b-none!" : "rounded-b-[calc(theme(borderRadius.2xl)-4px)]! mb-0!"}`}
>
<CardHeading>
<div className="text-muted-foreground font-medium text-sm">
Conversion Rate
</div>
<div className="flex items-center gap-1.5">
<span className="text-foreground font-semibold text-xl">
29.9%
</span>
<Badge
variant="primary"
appearance="outline"
size="sm"
shape="circle"
>
+4.5%
</Badge>
</div>
</CardHeading>
<CardToolbar>
<CollapsibleTrigger
render={
<Button variant="secondary">
Details
{isOpen ? <ChevronUp /> : <ChevronDown />}
</Button>
}
/>
</CardToolbar>
</CardHeader>
<CollapsiblePanel>
<CardContent className="text-sm space-y-3 rounded-t-none!">
{stats.map((stat,
index) => (
<div key={index} className="flex items-center justify-between">
<span className="text-muted-foreground">{stat.label}</span>
<div className="flex items-center space-x-2">
<span className="text-foreground font-semibold">
${stat.value.toLocaleString()}
</span>
<span
className={cn(
"flex items-center justify-end text-sm font-medium min-w-20",
stat.changeType === "increase"
? "text-success"
: "text-destructive",
)}
>
{stat.changeType === "increase" ? (
<ArrowUp className="size-3.5" />
) : (
<ArrowDown className="size-3.5" />
)}
{stat.change}
</span>
</div>
</div>
))}
</CardContent>
</CollapsiblePanel>
</Collapsible>
</Card>
);
}Default Open
Start the collapsible in an open state by default.
import { Button } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return (
<div className="w-full max-w-[500px] text-foreground text-sm rounded-lg border border-border p-4">
<div className="font-medium">Open by Default</div>
<Collapsible defaultOpen>
<CollapsiblePanel>
<div className="mt-3 text-muted-foreground">
This collapsible starts in the open state by default. Click the
trigger below to collapse it.
</div>
</CollapsiblePanel>
<div className="text-end mt-2">
<CollapsibleTrigger
render={
<Button underlined="solid" mode="link" size="sm">
Toggle
</Button>
}
/>
</div>
</Collapsible>
</div>
);
}Disabled
Prevent the collapsible from being toggled.
import {
Button,
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return (
<div className="w-full max-w-[500px] text-foreground text-sm rounded-lg border border-border p-4 opacity-50">
<div className="font-medium">Disabled Collapsible</div>
<Collapsible disabled>
<CollapsiblePanel>
<div className="mt-3 text-muted-foreground">
This content cannot be toggled because the collapsible is disabled.
</div>
</CollapsiblePanel>
<div className="text-end mt-2">
<CollapsibleTrigger
render={
<Button underlined="dashed" mode="link" size="sm" disabled>
Cannot toggle
</Button>
}
/>
</div>
</Collapsible>
</div>
);
}Nested
Create hierarchical collapsible sections by nesting multiple collapsibles.
"use client";
import {
Button,
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@tilt-legal/cubitt-components/primitives";
import { ChevronDown, ChevronUp } from "@tilt-legal/cubitt-icons/ui/outline";
export default function Component() {
const [parentOpen, setParentOpen] = React.useState(false);
const [child1Open, setChild1Open] = React.useState(false);
const [child2Open, setChild2Open] = React.useState(false);
return (
<div className="w-full max-w-[500px] text-foreground text-sm rounded-lg border border-border p-4">
<Collapsible open={parentOpen} onOpenChange={setParentOpen}>
<div className="flex items-center justify-between">
<div className="font-medium">Parent Section</div>
<CollapsibleTrigger
render={
<Button variant="ghost" mode="icon">
{parentOpen ? <ChevronUp /> : <ChevronDown />}
</Button>
}
/>
</div>
<CollapsiblePanel>
<div className="space-y-3 mt-3 pl-4 border-l-2 border-border">
{/* Child 1 */}
<Collapsible open={child1Open} onOpenChange={setChild1Open}>
<div className="flex items-center justify-between">
<div className="font-medium text-sm">Child Section 1</div>
<CollapsibleTrigger
render={
<Button variant="ghost" mode="icon">
{child1Open ? <ChevronUp /> : <ChevronDown />}
</Button>
}
/>
</div>
<CollapsiblePanel>
<div className="mt-2 text-muted-foreground text-sm">
Nested collapsible content for section 1.
</div>
</CollapsiblePanel>
</Collapsible>
{/* Child 2 */}
<Collapsible open={child2Open} onOpenChange={setChild2Open}>
<div className="flex items-center justify-between">
<div className="font-medium text-sm">Child Section 2</div>
<CollapsibleTrigger
render={
<Button variant="ghost" mode="icon">
{child2Open ? <ChevronUp /> : <ChevronDown />}
</Button>
}
/>
</div>
<CollapsiblePanel>
<div className="mt-2 text-muted-foreground text-sm">
Nested collapsible content for section 2.
</div>
</CollapsiblePanel>
</Collapsible>
</div>
</CollapsiblePanel>
</Collapsible>
</div>
);
}API Reference
Collapsible
The root component that manages the collapsible state.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | The controlled open state of the collapsible. |
defaultOpen | boolean | false | The default open state when uncontrolled. |
onOpenChange | (open: boolean) => void | - | Callback fired when the open state changes. |
disabled | boolean | false | Whether the collapsible is disabled. |
className | string | - | Additional CSS classes for the collapsible root. |
render | React.ReactElement | function | - | Allows replacing the component's HTML element. |
CollapsiblePanel
The panel component that contains the collapsible content with animated height transitions.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes for the panel. |
render | React.ReactElement | function | - | Allows replacing the component's HTML element. |
The panel includes built-in CSS transitions for smooth expand/collapse animations with automatic height calculation.
CollapsibleTrigger
The trigger component that toggles the collapsible state when clicked.
| Prop | Type | Default | Description |
|---|---|---|---|
render | React.ReactElement | function | - | Allows replacing the component's HTML element. Use this to render as a custom element like Button. |
className | string | - | Additional CSS classes for the trigger. |
When using the render prop, the trigger will forward all collapsible state to the rendered element.