Collapsible
A collapsible panel controlled by a button. Built with Base UI and Tailwind CSS. Copy-paste ready.
import { ChevronDownIcon } from "lucide-react";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
export default function Particle() {
return (
<Collapsible>
<CollapsibleTrigger className="inline-flex items-center gap-2 font-medium text-sm data-panel-open:[&_svg]:rotate-180">
Show recovery keys
<ChevronDownIcon className="size-4" />
</CollapsibleTrigger>
<CollapsiblePanel>
<ul className="flex flex-col gap-1 py-2 text-muted-foreground text-sm">
<li className="rounded-sm bg-muted px-2 py-1 font-mono">
4829-1735-6621
</li>
<li className="rounded-sm bg-muted px-2 py-1 font-mono">
9182-6407-5532
</li>
<li className="rounded-sm bg-muted px-2 py-1 font-mono">
3051-7924-9018
</li>
</ul>
</CollapsiblePanel>
</Collapsible>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/collapsible
Usage
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible"<Collapsible>
<CollapsibleTrigger>Can I access the file in the cloud?</CollapsibleTrigger>
<CollapsiblePanel>
Yes, you can access the file in the cloud.
</CollapsiblePanel>
</Collapsible>Examples
Default
A minimal trigger-and-panel pair — clicking the trigger toggles the panel's visibility with a smooth height animation.
Order #4189
import { ChevronsUpDownIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
export function Pattern() {
return (
<div className="h-48 w-full max-w-xs">
<Collapsible className="flex w-full flex-col gap-2">
<div className="flex items-center justify-between gap-4 px-2">
<h4 className="font-semibold text-sm">Order #4189</h4>
<CollapsibleTrigger
render={<Button className="size-8" size="icon" variant="ghost" />}
>
<ChevronsUpDownIcon aria-hidden="true" className="size-4" />
<span className="sr-only">Toggle details</span>
</CollapsibleTrigger>
</div>
<div className="flex items-center justify-between rounded-lg border bg-muted/30 px-3 py-2 text-sm">
<span className="text-muted-foreground">Status</span>
<Badge variant="success">Shipped</Badge>
</div>
<CollapsibleContent className="flex flex-col gap-2">
<div className="rounded-lg border px-3 py-2 text-sm">
<p className="font-medium">Shipping address</p>
<p className="text-muted-foreground">
100 Market St, San Francisco
</p>
</div>
<div className="rounded-lg border px-3 py-2 text-sm">
<p className="font-medium">Items</p>
<p className="text-muted-foreground">2x Studio Headphones</p>
</div>
</CollapsibleContent>
</Collapsible>
</div>
);
}
Animated Card
The collapsible is styled as a card with a header trigger and an animated content area that slides open and closed.
import { ChevronDownIcon } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
export function Pattern() {
return (
<div className="h-40 w-full max-w-xs">
<Card className="py-0">
<CardContent className="px-3">
<Collapsible>
<CollapsibleTrigger className="flex w-full cursor-pointer items-center justify-between gap-4 text-sm">
<span>How do I reset my password?</span>
<ChevronDownIcon
aria-hidden="true"
className="size-4 shrink-0 in-data-[state=open]:rotate-180 text-muted-foreground transition-transform"
/>
</CollapsibleTrigger>
<CollapsibleContent className="data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
<div className="pt-3 text-muted-foreground text-sm">
You can reset your password by clicking the "Forgot
Password" link on the login page. We'll send you an
email with instructions to create a new password.
</div>
</CollapsibleContent>
</Collapsible>
</CardContent>
</Card>
</div>
);
}
Bottom Trigger
Places the expand trigger at the bottom of the card — useful for "show more" patterns below a content preview.
import { ChevronDownIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardAction,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Progress } from "@/components/ui/progress";
export function Pattern() {
return (
<div className="h-72 w-full max-w-xs">
<Collapsible className="relative">
<Card>
<CardHeader className="flex items-center justify-between">
<CardTitle className="text-sm">3 days remaining in cycle</CardTitle>
<CardAction>
<Button size="sm" variant="outline">
Billing
</Button>
</CardAction>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2 rounded-lg border border-border bg-muted/60 p-3">
<div className="flex justify-between font-medium text-sm">
<span>$18.08 / $20</span>
<span>$200</span>
</div>
<Progress
className="**:data-[slot=progress-track]:h-1.5 **:data-[slot=progress-track]:bg-primary/20"
value={90}
/>
</div>
<CollapsibleContent className="data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
<div className="flex flex-col gap-2.5 pt-2">
{[
{ label: "Requests", value: "$210.84" },
{ label: "Active CPU", value: "$21.95" },
{ label: "Events", value: "$21.20" },
{ label: "Storage Usage", value: "$20.45" },
].map((item) => (
<div
className="flex justify-between text-xs"
key={item.label}
>
<span className="font-medium text-muted-foreground">
{item.label}
</span>
<span className="font-medium">{item.value}</span>
</div>
))}
</div>
</CollapsibleContent>
</CardContent>
</Card>
<div className="absolute -bottom-3.5 left-1/2 -translate-x-1/2">
<CollapsibleTrigger
render={
<Button
className="rounded-full bg-background! shadow-sm"
size="icon-sm"
variant="outline"
/>
}
>
<ChevronDownIcon
aria-hidden="true"
className="size-3.5 in-data-panel-open:rotate-180 transition-transform"
/>
<span className="sr-only">Toggle details</span>
</CollapsibleTrigger>
</div>
</Collapsible>
</div>
);
}
Form Fields
Hides advanced or optional form fields behind a collapsible to keep the form compact until the user needs them.
"use client";
import { Settings2Icon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Field, FieldLabel } from "@/components/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
} from "@/components/ui/input-group";
export function Pattern() {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="h-54 w-full max-w-xs">
<Card>
<CardHeader>
<CardTitle>Unit Pricing</CardTitle>
</CardHeader>
<CardContent>
<Collapsible
className="flex flex-col gap-3"
onOpenChange={setIsOpen}
open={isOpen}
>
<div className="flex items-end gap-2">
<Field className="flex-1">
<FieldLabel className="sr-only">Base Price</FieldLabel>
<InputGroup>
<InputGroupInput
defaultValue="19.00"
placeholder="0.00"
type="number"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>$</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
<CollapsibleTrigger
render={
<Button className="shrink-0" size="icon" variant="outline" />
}
>
<Settings2Icon aria-hidden="true" className="size-3.5" />
</CollapsibleTrigger>
</div>
<CollapsibleContent className="data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
<Field>
<FieldLabel>Tax Rate (%)</FieldLabel>
<InputGroup>
<InputGroupInput
defaultValue="15"
placeholder="0"
type="number"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>%</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
<Field>
<FieldLabel>Discount (%)</FieldLabel>
<InputGroup>
<InputGroupInput
defaultValue="0"
placeholder="0"
type="number"
/>
<InputGroupAddon align="inline-end">
<InputGroupText>%</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
</CollapsibleContent>
</Collapsible>
</CardContent>
</Card>
</div>
);
}
Multi-level Menu
A sidebar-style navigation where each section header is a collapsible trigger that reveals nested menu items.
"use client";
import {
BellIcon,
ChartBarIcon,
ChevronRightIcon,
CreditCardIcon,
FileTextIcon,
LayoutDashboardIcon,
MessageSquareIcon,
SettingsIcon,
ShieldIcon,
UserIcon,
} from "lucide-react";
import { type ReactElement, useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Item, ItemMedia, ItemTitle } from "@/components/ui/item";
type NavItem = {
id: string;
name: string;
icon: ReactElement;
items?: NavItem[];
};
const navItems: NavItem[] = [
{
icon: <LayoutDashboardIcon />,
id: "dashboard",
items: [
{
icon: <ChartBarIcon />,
id: "analytics",
items: [
{
icon: <FileTextIcon aria-hidden="true" />,
id: "real-time",
name: "Real-time",
},
{
icon: <FileTextIcon aria-hidden="true" />,
id: "historical",
name: "Historical",
},
],
name: "Analytics",
},
{
icon: <MessageSquareIcon aria-hidden="true" />,
id: "reports",
name: "Reports",
},
],
name: "Dashboard",
},
{
icon: <UserIcon aria-hidden="true" />,
id: "team",
items: [
{
icon: <UserIcon aria-hidden="true" />,
id: "members",
name: "Members",
},
{
icon: <ShieldIcon aria-hidden="true" />,
id: "permissions",
name: "Permissions",
},
],
name: "Team",
},
{
icon: <CreditCardIcon aria-hidden="true" />,
id: "billing",
name: "Billing",
},
{
icon: <SettingsIcon aria-hidden="true" />,
id: "settings",
name: "Settings",
},
{
icon: <BellIcon aria-hidden="true" />,
id: "notifications",
name: "Notifications",
},
];
function NavMenuItem({
item,
level = 0,
selectedId,
onSelect,
}: {
item: NavItem;
level?: number;
selectedId: string | null;
onSelect: (id: string) => void;
}) {
const isFolder = !!item.items && item.items.length > 0;
const isSelected = selectedId === item.id;
if (isFolder) {
return (
<Collapsible className="group/collapsible">
<CollapsibleTrigger
nativeButton={false}
render={
<Item
className="group/item cursor-pointer py-1.25 hover:bg-accent data-[state=open]:bg-accent"
size="xs"
style={{ paddingLeft: `${level * 12 + 8}px` }}
/>
}
>
<ItemMedia variant="icon">
<div className="size-3.5 text-muted-foreground group-hover/item:text-foreground">
{item.icon}
</div>
</ItemMedia>
<ItemTitle className="text-sm data-[state=open]/collapsible:font-semibold">
{item.name}
</ItemTitle>
<ChevronRightIcon
aria-hidden="true"
className="ml-auto size-4 in-data-open:rotate-90 text-muted-foreground transition-transform"
/>
</CollapsibleTrigger>
<CollapsibleContent className="overflow-hidden pt-0.5 data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
<div className="flex flex-col gap-0.5">
{item.items?.map((child) => (
<NavMenuItem
item={child}
key={child.id}
level={level + 1}
onSelect={onSelect}
selectedId={selectedId}
/>
))}
</div>
</CollapsibleContent>
</Collapsible>
);
}
return (
<Item
className="group/item cursor-pointer py-1.25 hover:bg-accent data-[active=true]:bg-accent data-[active=true]:text-foreground"
data-active={isSelected}
onClick={() => onSelect(item.id)}
size="xs"
style={{ paddingLeft: `${level * 12 + 8}px` }}
>
<ItemMedia variant="icon">
<div className="size-3.5 text-muted-foreground group-hover/item:text-foreground group-data-[active=true]/item:text-foreground">
{item.icon}
</div>
</ItemMedia>
<ItemTitle className="text-sm">{item.name}</ItemTitle>
</Item>
);
}
export function Pattern() {
const [selectedId, setSelectedId] = useState<string | null>("real-time");
return (
<div className="min-h-64 w-full max-w-56">
<Card className="p-0">
<CardContent className="p-1">
<div className="flex flex-col gap-0/5">
{navItems.map((item) => (
<NavMenuItem
item={item}
key={item.id}
onSelect={setSelectedId}
selectedId={selectedId}
/>
))}
</div>
</CardContent>
</Card>
</div>
);
}
Tree View
A file-tree implementation where directories are collapsible nodes and each item exposes hover actions for rename, copy, and delete.
"use client";
import {
ChevronRightIcon,
DownloadIcon,
FileIcon,
FolderIcon,
TrashIcon,
} from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Item,
ItemActions,
ItemMedia,
ItemTitle,
} from "@/components/ui/item";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
type FileTreeItem = { name: string } | { name: string; items: FileTreeItem[] };
const fileTree: FileTreeItem[] = [
{
items: [
{
items: [
{ name: "button.tsx" },
{ name: "card.tsx" },
{ name: "dialog.tsx" },
],
name: "ui",
},
{ name: "login-form.tsx" },
],
name: "components",
},
{
items: [{ name: "utils.ts" }, { name: "api.ts" }],
name: "lib",
},
{
items: [{ name: "use-debounce.ts" }, { name: "use-local-storage.ts" }],
name: "hooks",
},
{ name: "app.tsx" },
{ name: "package.json" },
];
function TreeItem({
item,
level = 0,
selectedId,
onSelect,
}: {
item: FileTreeItem;
level?: number;
selectedId?: string | null;
onSelect?: (name: string) => void;
}) {
const isFolder = "items" in item;
const isSelected = selectedId === item.name;
if (isFolder) {
return (
<Collapsible className="group/collapsible">
<CollapsibleTrigger
nativeButton={false}
render={
<Item
className="cursor-pointer py-1.5 hover:bg-accent data-[state=open]:bg-accent"
size="xs"
style={{ paddingLeft: `${level * 12 + 8}px` }}
/>
}
>
<ItemMedia variant="icon">
<ChevronRightIcon
aria-hidden="true"
className="size-3 in-data-open:rotate-90 text-muted-foreground transition-transform"
/>
<FolderIcon
aria-hidden="true"
className="size-3.5 text-muted-foreground group-hover/item:text-foreground"
/>
</ItemMedia>
<ItemTitle className="text-sm">{item.name}</ItemTitle>
</CollapsibleTrigger>
<CollapsibleContent className="overflow-hidden pt-0.5 data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
<div className="flex flex-col gap-0.5 ps-1.5">
{item.items.map((child) => (
<TreeItem
item={child}
key={child.name}
level={level + 1}
onSelect={onSelect}
selectedId={selectedId}
/>
))}
</div>
</CollapsibleContent>
</Collapsible>
);
}
return (
<Item
className="group/item cursor-pointer py-1.5 hover:bg-accent data-[active=true]:bg-accent"
data-active={isSelected}
onClick={() => onSelect?.(item.name)}
style={{ paddingLeft: `${level * 12 + 9}px` }}
>
<ItemMedia variant="icon">
<FileIcon
aria-hidden="true"
className="size-4 text-muted-foreground group-hover/item:text-foreground group-data-[active=true]/item:text-foreground"
/>
</ItemMedia>
<ItemTitle className="text-secondary-foreground text-sm group-hover/item:text-foreground group-data-[active=true]/item:text-foreground">
{item.name}
</ItemTitle>
<ItemActions className="-mr-2 ml-auto gap-0 opacity-0 transition-opacity group-hover/item:opacity-100 group-data-[active=true]/item:opacity-100">
<Button size="icon-xs" variant="ghost">
<DownloadIcon aria-hidden="true" />
</Button>
<Button size="icon-xs" variant="ghost">
<TrashIcon aria-hidden="true" />
</Button>
</ItemActions>
</Item>
);
}
export function Pattern() {
const [selectedId, setSelectedId] = useState<string | null>(null);
return (
<div className="min-h-64 w-72">
<Card className="gap-1 p-1">
<CardHeader className="p-0">
<Tabs defaultValue="explorer">
<TabsList className="h-8 w-full bg-accent p-1">
<TabsTrigger className="text-xs" value="explorer">
Explorer
</TabsTrigger>
<TabsTrigger className="text-xs" value="outline">
Outline
</TabsTrigger>
</TabsList>
</Tabs>
</CardHeader>
<CardContent className="p-0">
<div className="flex flex-col gap-0.5">
{fileTree.map((item) => (
<TreeItem
item={item}
key={item.name}
onSelect={setSelectedId}
selectedId={selectedId}
/>
))}
</div>
</CardContent>
</Card>
</div>
);
}
Read More
A product description card with truncated text and a gradient fade overlay. A "Read more" / "Show less" trigger with a rotating chevron expands and collapses the full content.
Designed for professionals who demand uncompromising audio fidelity, the Studio Pro X headphones deliver an immersive listening experience that redefines what wireless audio can be. The 40mm custom-tuned neodymium driver…
"use client";
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";
import {
Card,
CardDescription,
CardHeader,
CardPanel,
CardTitle,
} from "@/components/ui/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
const fullText = `Designed for professionals who demand uncompromising audio fidelity, the Studio Pro X headphones deliver an immersive listening experience that redefines what wireless audio can be. The 40mm custom-tuned neodymium drivers produce a wide, detailed soundstage with rich lows, clear mids, and airy highs — whether you're mixing a track or unwinding after a long day.
The adaptive active noise cancellation system continuously samples ambient sound up to 200 times per second, dynamically adjusting to your environment so you can stay focused in busy offices, flights, or city streets. When you need to be aware of your surroundings, Transparency mode lets in just the right amount of the outside world without removing the headphones.
Battery life is rated at 36 hours with ANC enabled, and a 10-minute quick charge gives you an additional 4 hours of playback. The ear cushions are crafted from premium memory foam wrapped in soft protein leather, providing a comfortable seal even during extended sessions.`;
const preview = `${fullText.slice(0, 220)}…`;
export function Pattern() {
const [open, setOpen] = useState(false);
return (
<div className="w-full max-w-md">
<Card>
<CardHeader>
<CardTitle>Studio Pro X Headphones</CardTitle>
<CardDescription>Wireless · ANC · 36h battery</CardDescription>
</CardHeader>
<CardPanel className="pt-0">
<Collapsible onOpenChange={setOpen} open={open}>
<div className="relative">
{!open && (
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-card to-transparent" />
)}
<p className="text-muted-foreground text-sm leading-relaxed">
{open ? fullText : preview}
</p>
<CollapsiblePanel>
<span />
</CollapsiblePanel>
</div>
<CollapsibleTrigger className="mt-2 inline-flex items-center gap-1 font-medium text-primary text-sm underline-offset-2 hover:underline">
{open ? "Show less" : "Read more"}
<ChevronDownIcon
className={`size-3.5 transition-transform duration-200 ${open ? "rotate-180" : ""}`}
/>
</CollapsibleTrigger>
</Collapsible>
</CardPanel>
</Card>
</div>
);
}
Build Log
Three pipeline step cards (install / typecheck / build) showing a status badge. Clicking a completed step expands a syntax-colored pre log panel. Pending steps are disabled.
//biome-ignore-all lint/suspicious/noArrayIndexKey: <>
import { ChevronDownIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Card, CardPanel } from "@/components/ui/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
type StepStatus = "success" | "failed" | "running" | "pending";
const statusVariant: Record<
StepStatus,
"success" | "destructive" | "info" | "outline"
> = {
failed: "destructive",
pending: "outline",
running: "info",
success: "success",
};
const steps: {
id: string;
name: string;
status: StepStatus;
duration: string;
log: string[];
}[] = [
{
duration: "3s",
id: "install",
log: [
"$ bun install",
"bun install v1.1.0",
"Resolving dependencies...",
"✓ 412 packages installed [3.14s]",
],
name: "Install dependencies",
status: "success",
},
{
duration: "12s",
id: "typecheck",
log: [
"$ tsc --noEmit",
"src/components/Button.tsx(14,7): error TS2322",
" Type 'string' is not assignable to type 'number'.",
"Found 1 error.",
],
name: "Type check",
status: "failed",
},
{
duration: "—",
id: "build",
log: [],
name: "Build",
status: "pending",
},
];
export function Pattern() {
return (
<div className="w-full max-w-md space-y-2">
{steps.map((step) => (
<Card className="overflow-hidden" key={step.id}>
<Collapsible disabled={step.status === "pending"}>
<CollapsibleTrigger className="w-full">
<CardPanel className="py-3">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2.5">
<Badge size="sm" variant={statusVariant[step.status]}>
{step.status}
</Badge>
<span className="font-medium text-sm">{step.name}</span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<span className="text-xs">{step.duration}</span>
{step.status !== "pending" && (
<ChevronDownIcon className="size-3.5 in-data-panel-open:rotate-180 transition-transform duration-200" />
)}
</div>
</div>
</CardPanel>
</CollapsibleTrigger>
{step.log.length > 0 && (
<CollapsiblePanel>
<div className="border-t bg-muted/40 px-4 py-3">
<pre className="space-y-0.5 overflow-x-auto text-xs leading-relaxed">
{step.log.map((line, i) => (
<div
className={
line.startsWith("$")
? "font-semibold text-foreground"
: line.includes("error") || line.includes("Error")
? "text-destructive"
: "text-muted-foreground"
}
key={i}
>
{line}
</div>
))}
</pre>
</div>
</CollapsiblePanel>
)}
</Collapsible>
</Card>
))}
</div>
);
}
Incident Timeline
An always-visible incident summary with a "View incident timeline" trigger that expands a vertical dot-and-line timeline with status badges and timestamps.
Started May 25, 2025 · 14:32 UTC
We are investigating reports of elevated API error rates affecting a subset of requests.
The issue has been identified as a misconfigured load balancer rule following a routine deployment at 14:15 UTC.
A fix has been deployed and rolled out to all regions. Error rates are returning to normal. We are monitoring the situation.
Next update in 12 minutes.
//biome-ignore-all lint/suspicious/noArrayIndexKey: <>
import { ChevronDownIcon, CircleAlertIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardHeader,
CardPanel,
CardTitle,
} from "@/components/ui/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Separator } from "@/components/ui/separator";
const timeline = [
{
message:
"We are investigating reports of elevated API error rates affecting a subset of requests.",
status: "investigating",
time: "14:32 UTC",
},
{
message:
"The issue has been identified as a misconfigured load balancer rule following a routine deployment at 14:15 UTC.",
status: "identified",
time: "14:48 UTC",
},
{
message:
"A fix has been deployed and rolled out to all regions. Error rates are returning to normal. We are monitoring the situation.",
status: "monitoring",
time: "15:04 UTC",
},
];
const statusVariant: Record<
string,
"warning" | "info" | "success" | "destructive"
> = {
identified: "warning",
investigating: "destructive",
monitoring: "success",
resolved: "success",
};
export function Pattern() {
return (
<div className="w-full max-w-md">
<Card>
<Collapsible defaultOpen>
<CardHeader className="border-b pb-3">
<div className="flex items-start justify-between gap-3">
<div className="flex items-start gap-2.5">
<CircleAlertIcon className="mt-0.5 size-4 shrink-0 text-warning" />
<div>
<CardTitle className="text-sm leading-snug">
Elevated API error rates
</CardTitle>
<p className="mt-0.5 text-muted-foreground text-xs">
Started May 25, 2025 · 14:32 UTC
</p>
</div>
</div>
<Badge size="sm" variant="warning">
Monitoring
</Badge>
</div>
<CollapsibleTrigger className="mt-2 inline-flex items-center gap-1 text-muted-foreground text-xs transition-colors hover:text-foreground">
View incident timeline
<ChevronDownIcon className="size-3 in-data-panel-open:rotate-180 transition-transform duration-200" />
</CollapsibleTrigger>
</CardHeader>
<CollapsiblePanel>
<CardPanel className="py-3">
<div className="relative">
<div className="absolute top-0 bottom-0 left-1.75 w-px bg-border" />
<div className="space-y-4">
{timeline.map((event, i) => (
<div className="relative flex gap-3" key={i}>
<div className="relative z-10 mt-0.5 flex size-3.5 shrink-0 items-center justify-center rounded-full border bg-background">
<div
className={`size-1.5 rounded-full ${
i === timeline.length - 1
? "bg-success"
: "bg-muted-foreground"
}`}
/>
</div>
<div className="min-w-0 flex-1 space-y-1">
<div className="flex items-center gap-2">
<Badge
size="sm"
variant={statusVariant[event.status] ?? "outline"}
>
{event.status}
</Badge>
<span className="text-muted-foreground text-xs">
{event.time}
</span>
</div>
<p className="text-muted-foreground text-xs leading-relaxed">
{event.message}
</p>
</div>
</div>
))}
</div>
</div>
<Separator className="mt-4 mb-3" />
<p className="text-muted-foreground text-xs">
Next update in{" "}
<span className="font-medium text-foreground">12 minutes</span>.
</p>
</CardPanel>
</CollapsiblePanel>
</Collapsible>
</Card>
</div>
);
}
FAQ Accordion
Four questions rendered as individual Collapsible components with single-open behavior — opening one automatically closes the others. A badge shows total question count.
Frequently Asked Questions
4 questions"use client";
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
const FAQS = [
{
answer:
"You can cancel at any time from your account settings. Your subscription remains active until the end of the current billing period.",
id: "cancel",
question: "How do I cancel my subscription?",
},
{
answer:
"Yes — we offer a 14-day free trial with no credit card required. You get full access to all Pro features during the trial.",
id: "trial",
question: "Is there a free trial?",
},
{
answer:
"We accept Visa, Mastercard, American Express, and PayPal. All transactions are secured with TLS encryption.",
id: "payment",
question: "What payment methods do you accept?",
},
{
answer:
"Absolutely. You can upgrade or downgrade your plan at any time and the price difference will be prorated automatically.",
id: "switch",
question: "Can I switch plans mid-cycle?",
},
];
export function Pattern() {
const [open, setOpen] = useState<string | null>(null);
return (
<div className="w-full max-w-md space-y-2">
<div className="mb-1 flex items-center justify-between">
<p className="font-semibold text-sm">Frequently Asked Questions</p>
<Badge size="sm" variant="secondary">
{FAQS.length} questions
</Badge>
</div>
{FAQS.map((faq) => (
<Collapsible
key={faq.id}
onOpenChange={(o) => setOpen(o ? faq.id : null)}
open={open === faq.id}
>
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-lg border px-4 py-3 text-left font-medium text-sm transition-colors hover:bg-muted/50">
{faq.question}
<ChevronDownIcon className="size-4 shrink-0 in-data-panel-open:rotate-180 text-muted-foreground transition-transform duration-200" />
</CollapsibleTrigger>
<CollapsiblePanel>
<div className="rounded-b-lg border border-t-0 bg-muted/30 px-4 py-3 text-muted-foreground text-sm leading-relaxed">
{faq.answer}
</div>
</CollapsiblePanel>
</Collapsible>
))}
</div>
);
}
Filter Panel
A sidebar filter panel where each filter group (Category, Status, Priority) is a collapsible section with checkboxes. An active-count badge and a clear-all button update as filters are toggled.
Filters
//biome-ignore-all lint/style/noNonNullAssertion:<>
"use client";
import { ChevronDownIcon, FilterIcon, XIcon } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
const FILTERS = [
{
id: "category",
label: "Category",
options: ["Design", "Engineering", "Marketing", "Operations"],
},
{
id: "status",
label: "Status",
options: ["Active", "Paused", "Archived"],
},
{
id: "priority",
label: "Priority",
options: ["High", "Medium", "Low"],
},
];
export function Pattern() {
const [selected, setSelected] = useState<Record<string, Set<string>>>({});
const toggle = (group: string, opt: string) =>
setSelected((prev) => {
const next = { ...prev, [group]: new Set(prev[group] ?? []) };
next[group]!.has(opt) ? next[group]!.delete(opt) : next[group]!.add(opt);
return next;
});
const totalActive = Object.values(selected).reduce(
(sum, s) => sum + s.size,
0,
);
return (
<div className="w-full max-w-xs space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<FilterIcon className="size-4 text-muted-foreground" />
<p className="font-semibold text-sm">Filters</p>
{totalActive > 0 && <Badge size="sm">{totalActive}</Badge>}
</div>
{totalActive > 0 && (
<button
className="flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground"
onClick={() => setSelected({})}
type="button"
>
<XIcon className="size-3" />
Clear all
</button>
)}
</div>
{FILTERS.map((f) => (
<Collapsible defaultOpen key={f.id}>
<CollapsibleTrigger className="flex w-full items-center justify-between py-2 font-medium text-sm transition-colors hover:text-primary">
<span className="flex items-center gap-2">
{f.label}
{(selected[f.id]?.size ?? 0) > 0 && (
<Badge size="sm" variant="secondary">
{selected[f.id]?.size}
</Badge>
)}
</span>
<ChevronDownIcon className="size-3.5 in-data-panel-open:rotate-180 text-muted-foreground transition-transform duration-200" />
</CollapsibleTrigger>
<CollapsiblePanel>
<div className="space-y-1.5 pb-3">
{f.options.map((opt) => (
<label
className="flex cursor-pointer items-center gap-2 text-muted-foreground text-sm hover:text-foreground"
key={opt}
>
<Checkbox
checked={selected[f.id]?.has(opt) ?? false}
onCheckedChange={() => toggle(f.id, opt)}
/>
{opt}
</label>
))}
</div>
</CollapsiblePanel>
</Collapsible>
))}
<Button className="w-full" size="sm">
Apply filters
</Button>
</div>
);
}
Package Dependencies
Three package groups (Middleware, Security, UI) rendered as cards with a version badge trigger. Expanding a card reveals a description and a list of package names in monospace.
Package dependencies
"use client";
import {
ChevronDownIcon,
PackageIcon,
ShieldIcon,
ZapIcon,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Card, CardPanel } from "@/components/ui/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
const PACKAGES = [
{
description: "Logging, rate limiting, and request tracing utilities.",
icon: ZapIcon,
id: "middleware",
items: ["@cnippet/logger", "@cnippet/rate-limit", "@cnippet/trace"],
label: "Middleware",
version: "v1.4.2",
},
{
description: "Input sanitization and schema validation.",
icon: ShieldIcon,
id: "security",
items: ["@cnippet/sanitize", "@cnippet/zod-adapter"],
label: "Security",
version: "v2.1.0",
},
{
description: "UI component bundles and design tokens.",
icon: PackageIcon,
id: "ui",
items: ["@cnippet/ui", "@cnippet/tokens", "@cnippet/icons"],
label: "UI",
version: "v3.0.0",
},
];
export function Pattern() {
return (
<div className="w-full max-w-sm space-y-2">
<p className="mb-3 font-semibold text-sm">Package dependencies</p>
{PACKAGES.map((pkg) => {
const Icon = pkg.icon;
return (
<Card className="overflow-hidden p-0" key={pkg.id}>
<Collapsible>
<CollapsibleTrigger className="w-full">
<CardPanel className="py-3">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2.5">
<Icon className="size-4 text-muted-foreground" />
<span className="font-medium text-sm">{pkg.label}</span>
<Badge size="sm" variant="secondary">
{pkg.version}
</Badge>
</div>
<ChevronDownIcon className="size-3.5 in-data-panel-open:rotate-180 text-muted-foreground transition-transform duration-200" />
</div>
</CardPanel>
</CollapsibleTrigger>
<CollapsiblePanel>
<div className="space-y-1.5 border-t bg-muted/30 px-4 py-3">
<p className="mb-2 text-muted-foreground text-xs">
{pkg.description}
</p>
{pkg.items.map((item) => (
<div
className="font-mono text-foreground text-xs"
key={item}
>
{item}
</div>
))}
</div>
</CollapsiblePanel>
</Collapsible>
</Card>
);
})}
</div>
);
}
Team Directory
A sidebar-style list of teams where each section is collapsible. Members show a colored presence dot, their name, and role beneath the team header.
Team directory
Aria Chen
Lead Designer
Leo Park
Product Designer
Kai Nguyen
Tech Lead
Sam Rivera
Frontend
Mia Johansson
Backend
James Okafor
Growth
Priya Singh
Content
"use client";
import { ChevronDownIcon, UserIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
type Member = {
id: string;
name: string;
role: string;
status: "active" | "away" | "offline";
};
const TEAMS: { id: string; label: string; members: Member[] }[] = [
{
id: "design",
label: "Design",
members: [
{ id: "m1", name: "Aria Chen", role: "Lead Designer", status: "active" },
{ id: "m2", name: "Leo Park", role: "Product Designer", status: "away" },
],
},
{
id: "engineering",
label: "Engineering",
members: [
{ id: "m3", name: "Kai Nguyen", role: "Tech Lead", status: "active" },
{ id: "m4", name: "Sam Rivera", role: "Frontend", status: "active" },
{ id: "m5", name: "Mia Johansson", role: "Backend", status: "offline" },
],
},
{
id: "marketing",
label: "Marketing",
members: [
{ id: "m6", name: "James Okafor", role: "Growth", status: "active" },
{ id: "m7", name: "Priya Singh", role: "Content", status: "away" },
],
},
];
const statusDot: Record<string, string> = {
active: "bg-emerald-500",
away: "bg-amber-500",
offline: "bg-muted-foreground/40",
};
export function Pattern() {
return (
<div className="w-full max-w-xs space-y-1">
<p className="mb-3 font-semibold text-sm">Team directory</p>
{TEAMS.map((team) => (
<Collapsible defaultOpen key={team.id}>
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md px-2 py-2 font-medium text-sm transition-colors hover:bg-muted/50">
<span className="flex items-center gap-2">
<UserIcon className="size-3.5 text-muted-foreground" />
{team.label}
<Badge size="sm" variant="secondary">
{team.members.length}
</Badge>
</span>
<ChevronDownIcon className="size-3.5 in-data-panel-open:rotate-180 text-muted-foreground transition-transform duration-200" />
</CollapsibleTrigger>
<CollapsiblePanel>
<div className="ml-5 space-y-0.5 border-l pb-1 pl-3">
{team.members.map((m) => (
<div
className="flex items-center gap-2 rounded-md px-2 py-1.5"
key={m.id}
>
<span
className={`size-1.5 shrink-0 rounded-full ${statusDot[m.status]}`}
/>
<div className="min-w-0">
<p className="text-sm leading-none">{m.name}</p>
<p className="mt-0.5 text-muted-foreground text-xs">
{m.role}
</p>
</div>
</div>
))}
</div>
</CollapsiblePanel>
</Collapsible>
))}
</div>
);
}
Privacy Policy
A card with four policy sections rendered as accordion-style collapsibles. Only one section is open at a time, driven by controlled open state.
Last updated June 3, 2026
We collect information you provide directly to us, such as when you create an account, make a purchase, or contact support. This includes name, email address, payment information, and any other information you choose to provide.
"use client";
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";
import {
Card,
CardHeader,
CardPanel,
CardTitle,
} from "@/components/ui/card";
import {
Collapsible,
CollapsiblePanel,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
const SECTIONS = [
{
description:
"We collect information you provide directly to us, such as when you create an account, make a purchase, or contact support. This includes name, email address, payment information, and any other information you choose to provide.",
id: "collection",
title: "What data we collect",
},
{
description:
"We use your information to operate and improve our services, process transactions, send service-related emails, and comply with legal obligations. We do not sell your personal data to third parties.",
id: "usage",
title: "How we use your data",
},
{
description:
"We share your information with service providers who assist us in operating our business, subject to confidentiality agreements. We may also disclose information when required by law.",
id: "sharing",
title: "Data sharing",
},
{
description:
"You may access, update, or delete your personal information at any time via your account settings. You may also opt out of marketing communications by clicking 'Unsubscribe' in any email.",
id: "rights",
title: "Your rights",
},
];
export function Pattern() {
const [open, setOpen] = useState<string | null>("collection");
return (
<div className="w-full max-w-md">
<Card>
<CardHeader className="border-b pb-3">
<CardTitle className="text-sm">Privacy Policy</CardTitle>
<p className="text-muted-foreground text-xs">
Last updated June 3, 2026
</p>
</CardHeader>
<CardPanel className="space-y-0 divide-y py-0">
{SECTIONS.map((s) => (
<Collapsible
key={s.id}
onOpenChange={(o) => setOpen(o ? s.id : null)}
open={open === s.id}
>
<CollapsibleTrigger className="flex w-full items-center justify-between py-3 text-left font-medium text-sm transition-colors hover:text-primary">
{s.title}
<ChevronDownIcon className="size-3.5 shrink-0 in-data-panel-open:rotate-180 text-muted-foreground transition-transform duration-200" />
</CollapsibleTrigger>
<CollapsiblePanel>
<p className="pb-3 text-muted-foreground text-xs leading-relaxed">
{s.description}
</p>
</CollapsiblePanel>
</Collapsible>
))}
</CardPanel>
</Card>
</div>
);
}

