Collapsible

An interactive component which expands/collapses a panel.

Client JS

Order #4189

StatusShipped
import { cn } from "cn"
import { ChevronsUpDown } from "@/components/icons"
import { buttonVariants } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

export default function CollapsibleDemo() {
  return (
    <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={cn(
            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

pnpm dlx shadcnui-hono-jsx@latest add collapsible

This component uses a small client script. add installs it into public/shadcn/; serve that directory at /shadcn/ and load the script on pages that use the component (see Client Scripts):

<script type="module" src="/shadcn/collapsible.js"></script>

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>
  )
}

Basic

Product details
This panel can be expanded or collapsed to reveal additional content.
import { cn } from "cn"
import { ChevronDownIcon } from "@/components/icons"
import { Button, buttonVariants } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

export function CollapsibleBasic() {
  return (
    <Card class="mx-auto w-full max-w-sm">
      <CardContent>
        {/* The trigger comes first, so the collapsible is a native <details>: open: variants style its state. */}
        <Collapsible class="group/collapsible rounded-md open:bg-muted">
          <CollapsibleTrigger
            class={cn(buttonVariants({ variant: "ghost", class: "w-full" }))}
          >
            Product details
            <ChevronDownIcon class="ml-auto group-open/collapsible: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.

Radius
Set the corner radius of the element.
import { MaximizeIcon, MinimizeIcon } from "@/components/icons"
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"

export function CollapsibleSettings() {
  return (
    <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" placeholder="0" value={0} />
            </Field>
            <Field>
              <FieldLabel for="radius-y" class="sr-only">
                Radius Y
              </FieldLabel>
              <Input id="radius" placeholder="0" value={0} />
            </Field>
            <CollapsibleContent class="col-span-full grid grid-cols-subgrid gap-2">
              <Field>
                <FieldLabel for="radius-x" class="sr-only">
                  Radius X
                </FieldLabel>
                <Input id="radius" placeholder="0" value={0} />
              </Field>
              <Field>
                <FieldLabel for="radius-y" class="sr-only">
                  Radius Y
                </FieldLabel>
                <Input id="radius" placeholder="0" value={0} />
              </Field>
            </CollapsibleContent>
          </FieldGroup>
          <CollapsibleTrigger
            class={buttonVariants({ variant: "outline", size: "icon" })}
          >
            <MinimizeIcon class="hidden group-data-panel-open/button:block" />
            <MaximizeIcon class="group-data-panel-open/button:hidden" />
          </CollapsibleTrigger>
        </Collapsible>
      </CardContent>
    </Card>
  )
}

File Tree

Use nested collapsibles to build a file tree.

components
ui
lib
hooks
types
public
import { cn } from "cn"
import { ChevronRightIcon, FileIcon, FolderIcon } from "@/components/icons"
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[] }

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) => {
    if ("items" in fileItem) {
      return (
        <Collapsible key={fileItem.name}>
          <CollapsibleTrigger
            class={cn(
              buttonVariants({
                variant: "ghost",
                size: "sm",
                class:
                  "group w-full justify-start transition-none hover:bg-accent hover:text-accent-foreground",
              })
            )}
          >
            <ChevronRightIcon class="transition-transform group-data-[state=open]:rotate-90" />
            <FolderIcon />
            {fileItem.name}
          </CollapsibleTrigger>
          <CollapsibleContent class="mt-1 ml-5 style-lyra:ml-4">
            <div class="flex flex-col gap-1">
              {fileItem.items.map((child) => renderItem(child))}
            </div>
          </CollapsibleContent>
        </Collapsible>
      )
    }
    return (
      <Button
        key={fileItem.name}
        variant="link"
        size="sm"
        class="w-full justify-start gap-2 text-foreground"
      >
        <FileIcon />
        <span>{fileItem.name}</span>
      </Button>
    )
  }

  return (
    <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">
          {fileTree.map((item) => renderItem(item))}
        </div>
      </CardContent>
    </Card>
  )
}

RTL

To enable RTL support in shadcn/ui, see the RTL configuration guide.

الطلب #4189

الحالةتم الشحن

// This example sets dir and uses useTranslation because this site is not RTL.

// In an RTL app you won't need them; see the RTL guide.

import { cn } from "cn"
import { ChevronsUpDown } from "@/components/icons"
import {
  type Translations,
  useTranslation,
} from "@/components/language-selector"
import { buttonVariants } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

const translations: 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 אוזניות סטודיו",
    },
  },
}

export function CollapsibleRtl() {
  const { dir, t } = useTranslation(translations, "ar")
  return (
    <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={cn(
            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>
  )
}

Notes

  • When CollapsibleTrigger is the first child of Collapsible, it is built on native <details>/<summary>: no JavaScript, but every other child is hidden while closed, not only CollapsibleContent, and state attributes (data-open, data-panel-open) are not rendered (use open: variants).
  • With the trigger anywhere else, the trigger is a <button> and the content is hidden while closed, like Base UI; toggling needs the client script /shadcn/collapsible.js (<script type="module" src="/shadcn/collapsible.js">), and Base UI's state attributes are rendered.
  • CollapsibleTrigger does not support render; style it with class (for example buttonVariants()). open/defaultOpen set the initial state; onOpenChange and disabled are not supported.
  • Accepts class instead of className.
  • Upstream documentation: shadcn/ui Collapsible. See also Differences from shadcn/ui.

API Reference

See the Base UI documentation for more information.