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/collapsible";<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 { Badge } from "@tilt-legal/cubitt-components/badge";
import { Button } from "@tilt-legal/cubitt-components/button";
import {
Card,
CardContent,
CardHeader,
CardHeading,
CardToolbar,
} from "@tilt-legal/cubitt-components/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@tilt-legal/cubitt-components/collapsible";
import {
ArrowDown,
ArrowUp,
ChevronDown,
ChevronUp,
} 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={cn(
"h-auto py-4 transition-[border-radius,margin]",
isOpen ? "rounded-b-none!" : "mb-0! rounded-b-xl!"
)}
>
<CardHeading>
<div className="font-medium text-fg-2 text-sm">
Conversion Rate
</div>
<div className="flex items-center gap-1.5">
<span className="font-semibold text-fg-1 text-xl">
29.9%
</span>
<Badge variant="brand" size="sm">
+4.5%
</Badge>
</div>
</CardHeading>
<CardToolbar>
<CollapsibleTrigger
render={
<Button variant="secondary">
Details
{isOpen ? <ChevronUp /> : <ChevronDown />}
</Button>
}
/>
</CardToolbar>
</CardHeader>
<CollapsiblePanel>
<CardContent className="space-y-3 rounded-t-none! text-sm">
{stats.map((stat) => (
<div key={stat.label} className="flex items-center justify-between">
<span className="text-fg-2">{stat.label}</span>
<div className="flex items-center space-x-2">
<span className="font-semibold text-fg-1">
${stat.value.toLocaleString()}
</span>
<span
className={cn(
"flex min-w-20 items-center justify-end font-medium text-sm",
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/button";
import {
Card,
CardContent,
CardFooter,
} from "@tilt-legal/cubitt-components/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@tilt-legal/cubitt-components/collapsible";
export default function Component() {
return (
<Card className="w-full max-w-[500px] text-sm">
<Collapsible defaultOpen>
<CardContent className="gap-0">
<div className="font-medium">Open by Default</div>
<CollapsiblePanel>
<div className="mt-3 text-fg-2">
This collapsible starts in the open state by default. Click the
trigger below to collapse it.
</div>
</CollapsiblePanel>
</CardContent>
<CardFooter className="justify-end">
<CollapsibleTrigger
render={
<Button size="sm" variant="link">
Toggle
</Button>
}
/>
</CardFooter>
</Collapsible>
</Card>
);
}Disabled
Prevent the collapsible from being toggled.
import { Button } from "@tilt-legal/cubitt-components/button";
import {
Card,
CardContent,
CardFooter,
} from "@tilt-legal/cubitt-components/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@tilt-legal/cubitt-components/collapsible";
export default function Component() {
return (
<Card className="w-full max-w-[500px] text-sm opacity-60">
<Collapsible disabled>
<CardContent className="gap-0">
<div className="font-medium">Disabled Collapsible</div>
<CollapsiblePanel>
<div className="mt-3 text-fg-2">
This content cannot be toggled because the collapsible is
disabled.
</div>
</CollapsiblePanel>
</CardContent>
<CardFooter className="justify-end">
<CollapsibleTrigger
render={
<Button disabled size="sm" variant="link">
Cannot toggle
</Button>
}
/>
</CardFooter>
</Collapsible>
</Card>
);
}Nested
Create hierarchical collapsible sections by nesting multiple collapsibles.
"use client";
import { Button } from "@tilt-legal/cubitt-components/button";
import { Card, CardContent } from "@tilt-legal/cubitt-components/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@tilt-legal/cubitt-components/collapsible";
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 (
<Card className="w-full max-w-[500px] text-sm">
<Collapsible open={parentOpen} onOpenChange={setParentOpen}>
<CardContent className="gap-0">
<div className="flex items-center justify-between">
<div className="font-medium">Parent Section</div>
<CollapsibleTrigger
render={
<Button mode="icon" variant="ghost">
{parentOpen ? <ChevronUp /> : <ChevronDown />}
</Button>
}
/>
</div>
<CollapsiblePanel>
<div className="mt-3 space-y-3 pl-4">
<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 mode="icon" variant="ghost">
{child1Open ? <ChevronUp /> : <ChevronDown />}
</Button>
}
/>
</div>
<CollapsiblePanel>
<div className="mt-2 text-fg-2 text-sm">
Nested collapsible content for section 1.
</div>
</CollapsiblePanel>
</Collapsible>
<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 mode="icon" variant="ghost">
{child2Open ? <ChevronUp /> : <ChevronDown />}
</Button>
}
/>
</div>
<CollapsiblePanel>
<div className="mt-2 text-fg-2 text-sm">
Nested collapsible content for section 2.
</div>
</CollapsiblePanel>
</Collapsible>
</div>
</CollapsiblePanel>
</CardContent>
</Collapsible>
</Card>
);
}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.