Github

Collapsible

An interactive component which expands/collapses a panel.

Order #4189

StatusShipped
"use client";

import { ChevronsUpDown } from "lucide-react";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
	Collapsible,
	CollapsibleContent,
	CollapsibleTrigger,
} from "@/components/ui/collapsible";

export function CollapsibleDemo() {
	const [isOpen, setIsOpen] = React.useState(false);

	return (
		<Collapsible
			open={isOpen}
			onOpenChange={setIsOpen}
			className="flex w-[350px] flex-col gap-2"
		>
			<div className="flex items-center justify-between gap-4 px-4">
				<h4 className="font-semibold text-sm">Order #4189</h4>
				<CollapsibleTrigger
					render={<Button variant="ghost" size="icon" className="size-8" />}
				>
					<ChevronsUpDown />
					<span className="sr-only">Toggle details</span>
				</CollapsibleTrigger>
			</div>
			<div className="flex items-center justify-between rounded-md border px-4 py-2 text-sm">
				<span className="text-muted-foreground">Status</span>
				<span className="font-medium">Shipped</span>
			</div>
			<CollapsibleContent className="flex flex-col gap-2">
				<div className="rounded-md border px-4 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-md border px-4 py-2 text-sm">
					<p className="font-medium">Items</p>
					<p className="text-muted-foreground">2x Studio Headphones</p>
				</div>
			</CollapsibleContent>
		</Collapsible>
	);
}

Installation

pnpm dlx shadcn@latest add https://herocn.dev/r/collapsible.json

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>

Composition

Use the following composition to build a Collapsible:

Collapsible
├── CollapsibleTrigger
└── CollapsibleContent

Controlled State

Use the open and onOpenChange props to control the state.

import * as React from "react"

export function Example() {
  const [open, setOpen] = React.useState(false)

  return (
    <Collapsible open={open} onOpenChange={setOpen}>
      <CollapsibleTrigger>Toggle</CollapsibleTrigger>
      <CollapsibleContent>Content</CollapsibleContent>
    </Collapsible>
  )
}

Examples

Basic

import { ChevronDownIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import {
	Collapsible,
	CollapsibleContent,
	CollapsibleTrigger,
} from "@/components/ui/collapsible";

export function CollapsibleBasic() {
	return (
		<Card className="mx-auto w-full max-w-sm">
			<CardContent>
				<Collapsible className="rounded-md data-open:bg-muted">
					<CollapsibleTrigger
						render={<Button variant="ghost" className="w-full" />}
					>
						Product details
						<ChevronDownIcon className="ml-auto group-data-panel-open/button:rotate-180" />
					</CollapsibleTrigger>
					<CollapsibleContent className="flex flex-col items-start gap-2 p-2.5 pt-1 text-sm">
						<div>
							This panel can be expanded or collapsed to reveal additional
							content.
						</div>
						<Button size="sm">Learn More</Button>
					</CollapsibleContent>
				</Collapsible>
			</CardContent>
		</Card>
	);
}

Settings Panel

Use a trigger button to reveal additional settings.

Radius
Set the corner radius of the element.
"use client";

import { MaximizeIcon, MinimizeIcon } from "lucide-react";
import * as React from "react";
import { Button } 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";

export function CollapsibleSettings() {
	const [isOpen, setIsOpen] = React.useState(false);

	return (
		<Card className="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
					open={isOpen}
					onOpenChange={setIsOpen}
					className="flex items-start gap-2"
				>
					<FieldGroup className="grid w-full grid-cols-2 gap-2">
						<Field>
							<FieldLabel htmlFor="radius-x" className="sr-only">
								Radius X
							</FieldLabel>
							<Input id="radius" placeholder="0" defaultValue={0} />
						</Field>
						<Field>
							<FieldLabel htmlFor="radius-y" className="sr-only">
								Radius Y
							</FieldLabel>
							<Input id="radius" placeholder="0" defaultValue={0} />
						</Field>
						<CollapsibleContent className="col-span-full grid grid-cols-subgrid gap-2">
							<Field>
								<FieldLabel htmlFor="radius-x" className="sr-only">
									Radius X
								</FieldLabel>
								<Input id="radius" placeholder="0" defaultValue={0} />
							</Field>
							<Field>
								<FieldLabel htmlFor="radius-y" className="sr-only">
									Radius Y
								</FieldLabel>
								<Input id="radius" placeholder="0" defaultValue={0} />
							</Field>
						</CollapsibleContent>
					</FieldGroup>
					<CollapsibleTrigger render={<Button variant="outline" size="icon" />}>
						{isOpen ? <MinimizeIcon /> : <MaximizeIcon />}
					</CollapsibleTrigger>
				</Collapsible>
			</CardContent>
		</Card>
	);
}

File Tree

Use nested collapsibles to build a file tree.

import { ChevronRightIcon, FileIcon, FolderIcon } from "lucide-react";
import { Button } 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[] };

export function CollapsibleFileTree() {
	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" },
	];

	const renderItem = (fileItem: FileTreeItem): React.ReactNode => {
		if ("items" in fileItem) {
			return (
				<Collapsible key={fileItem.name}>
					<CollapsibleTrigger
						render={
							<Button
								variant="ghost"
								size="sm"
								className="group w-full justify-start transition-none hover:bg-accent hover:text-accent-foreground"
							/>
						}
					>
						<ChevronRightIcon className="group-data-panel-open:rotate-90" />
						<FolderIcon />
						{fileItem.name}
					</CollapsibleTrigger>
					<CollapsibleContent className="mt-1 ml-5 style-lyra:ml-4">
						<div className="flex flex-col gap-1">
							{fileItem.items.map((child) => renderItem(child))}
						</div>
					</CollapsibleContent>
				</Collapsible>
			);
		}

		return (
			<Button
				key={fileItem.name}
				variant="ghost"
				size="sm"
				className="w-full justify-start gap-2 text-foreground"
			>
				<FileIcon />
				<span>{fileItem.name}</span>
			</Button>
		);
	};

	return (
		<Card className="mx-auto w-full max-w-[16rem] gap-2" size="sm">
			<CardHeader>
				<Tabs defaultValue="explorer">
					<TabsList className="w-full">
						<TabsTrigger value="explorer">Explorer</TabsTrigger>
						<TabsTrigger value="settings">Outline</TabsTrigger>
					</TabsList>
				</Tabs>
			</CardHeader>
			<CardContent>
				<div className="flex max-h-107 flex-col gap-1 overflow-auto px-1">
					{fileTree.map((item) => renderItem(item))}
				</div>
			</CardContent>
		</Card>
	);
}

RTL

"use client";

import { ChevronDownIcon } from "lucide-react";

import {
	type Translations,
	useTranslation,
} from "@/components/language-selector";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import {
	Collapsible,
	CollapsibleContent,
	CollapsibleTrigger,
} from "@/components/ui/collapsible";

const translations: Translations = {
	en: {
		dir: "ltr",
		values: {
			trigger: "Frequently asked questions",
			answer:
				"Yes. Free to use for personal and commercial projects. No attribution required.",
			learnMore: "Learn More",
		},
	},
	ar: {
		dir: "rtl",
		values: {
			trigger: "الأسئلة الشائعة",
			answer:
				"نعم. مجاني للاستخدام في المشاريع الشخصية والتجارية. لا يلزم ذكر المصدر.",
			learnMore: "اعرف المزيد",
		},
	},
	he: {
		dir: "rtl",
		values: {
			trigger: "שאלות נפוצות",
			answer: "כן. חינמי לשימוש בפרויקטים אישיים ומסחריים. אין צורך בייחוס.",
			learnMore: "למד עוד",
		},
	},
};

export function CollapsibleRtl() {
	const { dir, language, t } = useTranslation(translations, "ar");

	return (
		<Card className="mx-auto w-full max-w-sm" lang={language} dir={dir}>
			<CardContent>
				<Collapsible className="rounded-md data-open:bg-muted">
					<CollapsibleTrigger
						render={<Button variant="ghost" className="w-full" />}
					>
						{t.trigger}
						<ChevronDownIcon className="ms-auto group-data-panel-open/button:rotate-180" />
					</CollapsibleTrigger>
					<CollapsibleContent className="flex flex-col items-start gap-2 p-2.5 pt-1 text-sm">
						<div>{t.answer}</div>
						<Button size="sm">{t.learnMore}</Button>
					</CollapsibleContent>
				</Collapsible>
			</CardContent>
		</Card>
	);
}

API Reference

See the Base UI documentation for more information.