Order #4189
Shipping address
100 Market St, San Francisco
Items
2x Studio Headphones
---
import ChevronsUpDown from "@lucide/astro/icons/chevrons-up-down"
import { buttonVariants } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
---
<Collapsible class="flex w-[350px] flex-col gap-2">
<div class="flex items-center justify-between gap-4 px-4">
<h4 class="text-sm font-semibold">Order #4189</h4>
<CollapsibleTrigger
class={buttonVariants({
variant: "ghost",
size: "icon",
class: "size-8",
})}
>
<ChevronsUpDown />
<span class="sr-only">Toggle details</span>
</CollapsibleTrigger>
</div>
<div class="flex items-center justify-between rounded-md border px-4 py-2 text-sm">
<span class="text-muted-foreground">Status</span>
<span class="font-medium">Shipped</span>
</div>
<CollapsibleContent class="flex flex-col gap-2">
<div class="rounded-md border px-4 py-2 text-sm">
<p class="font-medium">Shipping address</p>
<p class="text-muted-foreground">100 Market St, San Francisco</p>
</div>
<div class="rounded-md border px-4 py-2 text-sm">
<p class="font-medium">Items</p>
<p class="text-muted-foreground">2x Studio Headphones</p>
</div>
</CollapsibleContent>
</Collapsible>Installation
npx shadcn@latest add @fulldev/collapsible
Usage
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
<Collapsible>
<CollapsibleTrigger>Can I use this in my project?</CollapsibleTrigger>
<CollapsibleContent>
Yes. Free to use for personal and commercial projects. No attribution
required.
</CollapsibleContent>
</Collapsible>
CollapsibleTrigger renders a button. To style it as a button, pass
class={buttonVariants({ variant: "ghost" })}.
Composition
Collapsible
├── CollapsibleTrigger
└── CollapsibleContent
Open State
Use defaultOpen to render the collapsible expanded on first load. The root
fires collapsible:change with detail.open and accepts a collapsible:set
event with { open: boolean } to open or close it from a script.
<Collapsible defaultOpen>
<CollapsibleTrigger>Toggle</CollapsibleTrigger>
<CollapsibleContent>Content</CollapsibleContent>
</Collapsible>
Dismissal
Use closeOnEscape and closeOnClickOutside for disclosures such as a navigation
panel. Both are off by default. Escape returns focus to the trigger when focus
was inside the collapsible. Nested popovers and dialogs close before the panel,
and their portaled content counts as inside it.
Open the panel, then press Escape or click outside it to close.
---
import ChevronDownIcon from "@lucide/astro/icons/chevron-down"
import { buttonVariants } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
---
<div class="mx-auto flex w-full max-w-xs flex-col gap-4">
<Collapsible closeOnEscape closeOnClickOutside class="flex flex-col gap-2">
<CollapsibleTrigger
class={buttonVariants({ variant: "outline", class: "w-full" })}
>
Browse documentation
<ChevronDownIcon class="ml-auto group-aria-expanded/button:rotate-180" />
</CollapsibleTrigger>
<CollapsibleContent>
<nav aria-label="Documentation example" class="flex flex-col gap-1">
<a
href="/docs/installation/"
class={buttonVariants({ variant: "ghost", class: "justify-start" })}
>
Installation
</a>
<a
href="/components/accordion/"
class={buttonVariants({ variant: "ghost", class: "justify-start" })}
>
Accordion
</a>
<a
href="/components/collapsible/"
class={buttonVariants({ variant: "ghost", class: "justify-start" })}
>
Collapsible
</a>
</nav>
</CollapsibleContent>
</Collapsible>
<p class="text-muted-foreground text-sm">
Open the panel, then press Escape or click outside it to close.
</p>
</div>Basic
Style the open state with data-[state=open]: on the root and
group-aria-expanded/button: inside a trigger styled with buttonVariants.
---
import ChevronDownIcon from "@lucide/astro/icons/chevron-down"
import { Button, buttonVariants } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
---
<Card class="mx-auto w-full max-w-sm">
<CardContent>
<Collapsible class="data-[state=open]:bg-muted rounded-md">
<CollapsibleTrigger
class={buttonVariants({ variant: "ghost", class: "w-full" })}
>
Product details
<ChevronDownIcon class="ml-auto group-aria-expanded/button:rotate-180" />
</CollapsibleTrigger>
<CollapsibleContent class="flex flex-col items-start gap-2 p-2.5 pt-0 text-sm">
<div>
This panel can be expanded or collapsed to reveal additional content.
</div>
<Button size="xs">Learn More</Button>
</CollapsibleContent>
</Collapsible>
</CardContent>
</Card>Settings Panel
Use a trigger button to reveal additional settings.
---
import MaximizeIcon from "@lucide/astro/icons/maximize"
import MinimizeIcon from "@lucide/astro/icons/minimize"
import { buttonVariants } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
---
<Card class="mx-auto w-full max-w-xs" size="sm">
<CardHeader>
<CardTitle>Radius</CardTitle>
<CardDescription>Set the corner radius of the element.</CardDescription>
</CardHeader>
<CardContent>
<Collapsible class="flex items-start gap-2">
<FieldGroup class="grid w-full grid-cols-2 gap-2">
<Field>
<FieldLabel for="radius-x" class="sr-only">
Radius X
</FieldLabel>
<Input id="radius-x" placeholder="0" value="0" />
</Field>
<Field>
<FieldLabel for="radius-y" class="sr-only">
Radius Y
</FieldLabel>
<Input id="radius-y" placeholder="0" value="0" />
</Field>
<CollapsibleContent class="col-span-full grid grid-cols-subgrid gap-2">
<Field>
<FieldLabel for="radius-x-2" class="sr-only">
Radius X
</FieldLabel>
<Input id="radius-x-2" placeholder="0" value="0" />
</Field>
<Field>
<FieldLabel for="radius-y-2" class="sr-only">
Radius Y
</FieldLabel>
<Input id="radius-y-2" placeholder="0" value="0" />
</Field>
</CollapsibleContent>
</FieldGroup>
<CollapsibleTrigger
class={buttonVariants({ variant: "outline", size: "icon" })}
>
<MaximizeIcon class="group-aria-expanded/button:hidden" />
<MinimizeIcon class="hidden group-aria-expanded/button:block" />
</CollapsibleTrigger>
</Collapsible>
</CardContent>
</Card>File Tree
Use nested collapsibles to build a file tree.
---
import ChevronRightIcon from "@lucide/astro/icons/chevron-right"
import FileIcon from "@lucide/astro/icons/file"
import FolderIcon from "@lucide/astro/icons/folder"
import { Button, buttonVariants } from "@/components/ui/button"
import { Card, CardContent, CardHeader } from "@/components/ui/card"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
type FileTreeItem = { name: string } | { name: string; items: FileTreeItem[] }
const fileTree: FileTreeItem[] = [
{
name: "components",
items: [
{
name: "ui",
items: [
{ name: "button.tsx" },
{ name: "card.tsx" },
{ name: "dialog.tsx" },
{ name: "input.tsx" },
{ name: "select.tsx" },
{ name: "table.tsx" },
],
},
{ name: "login-form.tsx" },
{ name: "register-form.tsx" },
],
},
{
name: "lib",
items: [{ name: "utils.ts" }, { name: "cn.ts" }, { name: "api.ts" }],
},
{
name: "hooks",
items: [
{ name: "use-media-query.ts" },
{ name: "use-debounce.ts" },
{ name: "use-local-storage.ts" },
],
},
{
name: "types",
items: [{ name: "index.d.ts" }, { name: "api.d.ts" }],
},
{
name: "public",
items: [{ name: "favicon.ico" }, { name: "logo.svg" }, { name: "images" }],
},
{ name: "app.tsx" },
{ name: "layout.tsx" },
{ name: "globals.css" },
{ name: "package.json" },
{ name: "tsconfig.json" },
{ name: "README.md" },
{ name: ".gitignore" },
]
// Renders the card, or one tree level when Astro.self passes `items`.
const { items } = Astro.props as { items?: FileTreeItem[] }
---
{items ? (
items.map((fileItem) =>
"items" in fileItem ? (
<Collapsible>
<CollapsibleTrigger
class={buttonVariants({
variant: "ghost",
size: "sm",
class:
"group hover:bg-accent hover:text-accent-foreground w-full justify-start transition-none",
})}
>
<ChevronRightIcon class="transition-transform group-data-[state=open]:rotate-90" />
<FolderIcon />
{fileItem.name}
</CollapsibleTrigger>
<CollapsibleContent class="style-lyra:ml-4 mt-1 ml-5">
<div class="flex flex-col gap-1">
<Astro.self items={fileItem.items} />
</div>
</CollapsibleContent>
</Collapsible>
) : (
<Button
variant="link"
size="sm"
class="text-foreground w-full justify-start gap-2"
>
<FileIcon />
<span>{fileItem.name}</span>
</Button>
)
)
) : (
<Card class="mx-auto w-full max-w-[16rem] gap-2" size="sm">
<CardHeader>
<Tabs defaultValue="explorer">
<TabsList class="w-full">
<TabsTrigger value="explorer">Explorer</TabsTrigger>
<TabsTrigger value="settings">Outline</TabsTrigger>
</TabsList>
</Tabs>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-1">
<Astro.self items={fileTree} />
</div>
</CardContent>
</Card>
)}RTL
الطلب #4189
عنوان الشحن
100 Market St, San Francisco
العناصر
2x سماعات الاستوديو
---
import ChevronsUpDown from "@lucide/astro/icons/chevrons-up-down"
import { buttonVariants } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
const translations = {
en: {
dir: "ltr",
values: {
orderNumber: "Order #4189",
status: "Status",
shipped: "Shipped",
shippingAddress: "Shipping address",
address: "100 Market St, San Francisco",
items: "Items",
itemsDescription: "2x Studio Headphones",
},
},
ar: {
dir: "rtl",
values: {
orderNumber: "الطلب #4189",
status: "الحالة",
shipped: "تم الشحن",
shippingAddress: "عنوان الشحن",
address: "100 Market St, San Francisco",
items: "العناصر",
itemsDescription: "2x سماعات الاستوديو",
},
},
he: {
dir: "rtl",
values: {
orderNumber: "הזמנה #4189",
status: "סטטוס",
shipped: "נשלח",
shippingAddress: "כתובת משלוח",
address: "100 Market St, San Francisco",
items: "פריטים",
itemsDescription: "2x אוזניות סטודיו",
},
},
}
const { dir, values: t } = translations.ar
---
<Collapsible class="flex w-[350px] flex-col gap-2" dir={dir}>
<div class="flex items-center justify-between gap-4 px-4">
<h4 class="text-sm font-semibold">{t.orderNumber}</h4>
<CollapsibleTrigger
class={buttonVariants({
variant: "ghost",
size: "icon",
class: "size-8",
})}
>
<ChevronsUpDown />
<span class="sr-only">Toggle details</span>
</CollapsibleTrigger>
</div>
<div class="flex items-center justify-between rounded-md border px-4 py-2 text-sm">
<span class="text-muted-foreground">{t.status}</span>
<span class="font-medium">{t.shipped}</span>
</div>
<CollapsibleContent class="flex flex-col gap-2">
<div class="rounded-md border px-4 py-2 text-sm">
<p class="font-medium">{t.shippingAddress}</p>
<p class="text-muted-foreground">{t.address}</p>
</div>
<div class="rounded-md border px-4 py-2 text-sm">
<p class="font-medium">{t.items}</p>
<p class="text-muted-foreground">{t.itemsDescription}</p>
</div>
</CollapsibleContent>
</Collapsible>API Reference
Source: src/components/ui/collapsible
| Prop | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | false | Opens the content on first load. |
hiddenUntilFound | boolean | false | Uses hidden="until-found" so find-in-page reveals content. |
closeOnEscape | boolean | false | Closes on Escape and restores focus to the trigger when focus was inside. |
closeOnClickOutside | boolean | false | Closes on a pointer press outside the collapsible. |
The root and the content get data-state="open" or data-state="closed", and
the trigger gets aria-expanded. See the
data-slot docs
for the events.
For stacked FAQ patterns with multiple items, prefer Accordion.