Keep supporting details out of the way until they are needed. Pair a trigger with content, and nest panels for tree-like layouts.
tsx
import { Card, CardContent } from "@/components/ui/card"import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="m6 9 6 6 6-6" /> </svg> )}export function Example() { return ( <Card className="w-full py-0"> <CardContent className="py-4"> <Collapsible className="flex w-full flex-col"> <div className="flex items-center justify-between gap-3"> <h4 className="text-sm font-medium tracking-wide"> Registry push #2841 </h4> <CollapsibleTrigger className="group gap-1.5 rounded-md px-2.5 py-1.5 hover:bg-accent hover:text-accent-foreground"> Details <ChevronDownIcon className="size-4 transition-transform duration-200 ease-out group-data-[state=open]:rotate-180" /> </CollapsibleTrigger> </div> <CollapsibleContent className="flex flex-col gap-2 pt-3 text-sm tracking-wide text-muted-foreground"> <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2"> <span>Status</span> <span className="text-foreground">Shipped</span> </div> <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2"> <span>Namespace</span> <span className="text-foreground">@arctis-sh/ui</span> </div> </CollapsibleContent> </Collapsible> </CardContent> </Card> )}Installation
npx @arctis-sh/@arctis-sh/ui@latest add collapsibleUsage
import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible"<Collapsible> <CollapsibleTrigger>What’s included?</CollapsibleTrigger> <CollapsibleContent> You get the source. Install the component, then adapt it to your app. </CollapsibleContent></Collapsible>Composition
Collapsible ├── CollapsibleTrigger └── CollapsibleContent
Basic
A card header with a trigger that reveals status rows beneath the title.
tsx
import { Card, CardContent } from "@/components/ui/card"import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="m6 9 6 6 6-6" /> </svg> )}export function Example() { return ( <Card className="w-full py-0"> <CardContent className="py-4"> <Collapsible className="flex w-full flex-col"> <div className="flex items-center justify-between gap-3"> <h4 className="text-sm font-medium tracking-wide"> Registry push #2841 </h4> <CollapsibleTrigger className="group gap-1.5 rounded-md px-2.5 py-1.5 hover:bg-accent hover:text-accent-foreground"> Details <ChevronDownIcon className="size-4 transition-transform duration-200 ease-out group-data-[state=open]:rotate-180" /> </CollapsibleTrigger> </div> <CollapsibleContent className="flex flex-col gap-2 pt-3 text-sm tracking-wide text-muted-foreground"> <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2"> <span>Status</span> <span className="text-foreground">Shipped</span> </div> <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2"> <span>Namespace</span> <span className="text-foreground">@arctis-sh/ui</span> </div> </CollapsibleContent> </Collapsible> </CardContent> </Card> )}Controlled
Pass open and onOpenChange when the parent owns the state. Use defaultOpen to set an initial uncontrolled state.
tsx
import * as React from "react"import { Card, CardContent } from "@/components/ui/card"import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="m6 9 6 6 6-6" /> </svg> )}export function Example() { const [open, setOpen] = React.useState(false) return ( <Card className="w-full bg-transparent py-0"> <CardContent className="py-4"> <Collapsible open={open} onOpenChange={setOpen} className="flex w-full flex-col" > <CollapsibleTrigger className="group w-full justify-between text-left"> Theme tokens {open ? "(open)" : "(closed)"} <ChevronDownIcon className="size-4 transition-transform duration-200 ease-out group-data-[state=open]:rotate-180" /> </CollapsibleTrigger> <CollapsibleContent className="pt-3 text-sm tracking-wide text-muted-foreground"> CSS variables under globals.css drive light and dark surfaces for every component page. </CollapsibleContent> </Collapsible> </CardContent> </Card> )}Disabled
Set disabled on the root to lock the trigger. Dim the row as well so it reads as inactive.
tsx
import { Card, CardContent } from "@/components/ui/card"import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="m6 9 6 6 6-6" /> </svg> )}export function Example() { return ( <Card className="w-full bg-transparent py-0"> <CardContent className="py-4"> <Collapsible disabled className="flex w-full flex-col"> <CollapsibleTrigger className="group w-full justify-between text-left"> Publish package — unlock after review <ChevronDownIcon className="size-4" /> </CollapsibleTrigger> <CollapsibleContent className="pt-3 text-sm tracking-wide text-muted-foreground"> Shipping is locked until the changelog entry is approved. </CollapsibleContent> </Collapsible> </CardContent> </Card> )}Settings panel
Place a compact trigger beside a label, then reveal the related controls below it.
tsx
import * as React from "react"import { Card, CardContent } from "@/components/ui/card"import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible"function CornersIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="M8 3H5a2 2 0 0 0-2 2v3" /> <path d="M16 3h3a2 2 0 0 1 2 2v3" /> <path d="M8 21H5a2 2 0 0 1-2-2v-3" /> <path d="M16 21h3a2 2 0 0 0 2-2v-3" /> </svg> )}function ShrinkCornersIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <g transform="rotate(180 5.5 5.5)"> <path d="M8 3H5a2 2 0 0 0-2 2v3" /> </g> <g transform="rotate(180 18.5 5.5)"> <path d="M16 3h3a2 2 0 0 1 2 2v3" /> </g> <g transform="rotate(180 5.5 18.5)"> <path d="M8 21H5a2 2 0 0 1-2-2v-3" /> </g> <g transform="rotate(180 18.5 18.5)"> <path d="M16 21h3a2 2 0 0 0 2-2v-3" /> </g> </svg> )}export function Example() { const [open, setOpen] = React.useState(false) return ( <Card className="w-full bg-transparent py-0"> <CardContent className="py-4"> <Collapsible open={open} onOpenChange={setOpen} className="flex w-full flex-col gap-3" > <div className="grid gap-1"> <p className="text-sm font-medium tracking-wide">Radius</p> <p className="text-sm text-muted-foreground"> Set the corner radius of the element. </p> </div> <div className="flex items-start gap-2"> <div className="min-w-0 flex-1"> <div className="grid grid-cols-2 gap-2"> <input defaultValue="0" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" /> <input defaultValue="0" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" /> </div> <CollapsibleContent className="pt-2"> <div className="grid grid-cols-2 gap-2"> <input defaultValue="0" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" /> <input defaultValue="0" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" /> </div> </CollapsibleContent> </div> <CollapsibleTrigger className="inline-flex size-9 shrink-0 items-center justify-center rounded-md border border-border hover:bg-accent hover:text-accent-foreground"> {open ? ( <ShrinkCornersIcon className="size-4" /> ) : ( <CornersIcon className="size-4" /> )} <span className="sr-only">Toggle radius settings</span> </CollapsibleTrigger> </div> </Collapsible> </CardContent> </Card> )}File tree
Nest collapsibles to build a folder tree. The chevron rotates as each branch opens.
Explorer
button.tsx
card.tsx
collapsible.tsx
logo.tsx
globals.css
package.json
tsx
import type { ReactNode } from "react"import { Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronRightIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="m9 18 6-6-6-6" /> </svg> )}function FolderIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.64 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" /> </svg> )}function FileIcon(props: React.SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /> <path d="M14 2v4a2 2 0 0 0 2 2h4" /> </svg> )}function TreeFolder({ name, defaultOpen = false, children,}: { name: string defaultOpen?: boolean children: ReactNode}) { return ( <Collapsible defaultOpen={defaultOpen} className="flex flex-col"> <CollapsibleTrigger className="group w-fit justify-start gap-1.5 rounded-md px-1.5 py-1 text-left text-sm tracking-wide hover:bg-accent hover:text-accent-foreground"> <ChevronRightIcon className="size-3.5 shrink-0 transition-transform duration-200 ease-out group-data-[state=open]:rotate-90" /> <FolderIcon className="size-3.5 shrink-0 text-muted-foreground" /> {name} </CollapsibleTrigger> <CollapsibleContent className="ms-3 flex flex-col gap-1 border-l border-border ps-2 pt-1"> {children} </CollapsibleContent> </Collapsible> )}function TreeFile({ name }: { name: string }) { return ( <div className="inline-flex w-fit items-center gap-1.5 rounded-md px-1.5 py-1 text-sm tracking-wide text-muted-foreground hover:bg-accent hover:text-accent-foreground"> <span className="size-3.5 shrink-0" /> <FileIcon className="size-3.5 shrink-0" /> {name} </div> )}export function Example() { return ( <div className="w-full rounded-md border border-border p-3"> <p className="mb-2 px-1.5 text-xs tracking-wide text-muted-foreground"> Explorer </p> <TreeFolder name="components" defaultOpen> <TreeFolder name="ui" defaultOpen> <TreeFile name="button.tsx" /> <TreeFile name="card.tsx" /> <TreeFile name="collapsible.tsx" /> </TreeFolder> <TreeFile name="logo.tsx" /> </TreeFolder> <TreeFolder name="lib"> <TreeFile name="utils.ts" /> </TreeFolder> <TreeFile name="globals.css" /> <TreeFile name="package.json" /> </div> )}API Reference
Collapsible
| Prop | Type | Default |
|---|---|---|
| open | boolean | — |
| defaultOpen | boolean | false |
| onOpenChange | (open: boolean) => void | — |
| disabled | boolean | false |
| className | string | — |
<Collapsible> <CollapsibleTrigger>Toggle</CollapsibleTrigger> <CollapsibleContent>Hidden content</CollapsibleContent></Collapsible>CollapsibleTrigger
| Prop | Type | Default |
|---|---|---|
| disabled | boolean | false |
| className | string | — |
<CollapsibleTrigger>Toggle</CollapsibleTrigger>CollapsibleContent
| Prop | Type | Default |
|---|---|---|
| unmountOnExit | boolean | false |
| className | string | — |
<CollapsibleContent>Hidden content</CollapsibleContent>