# Shadcn Collapsible

> Free shadcn/ui collapsible for React — built on Radix. An accessible show/hide disclosure: a controlled toggle, a chevron trigger, show-more text, a sidebar filter group, nested trees, and expandable rows.

Source: https://designrevision.com/components/collapsible

The **Shadcn Collapsible** is the simplest disclosure: an accessible **show/hide** for a single region, built on <a href="https://www.radix-ui.com/primitives/docs/components/collapsible" rel="nofollow">Radix</a>. Three parts — root, trigger, content — and Radix handles the state, focus, and ARIA. The examples below cover a controlled toggle, a rotating chevron, show-more text, a filter group, nested trees, and expandable rows.

## Three parts

```tsx
<Collapsible>
  <CollapsibleTrigger>Toggle</CollapsibleTrigger>
  <CollapsibleContent>Hidden until opened.</CollapsibleContent>
</Collapsible>
```

`Collapsible` owns the open state, `CollapsibleTrigger` toggles it, and `CollapsibleContent` is shown or hidden.

## Collapsible vs Accordion

They look similar but solve different problems:

- **Collapsible** — toggle **one** region open or closed.
- **[Accordion](/components/accordion)** — a **set** of items where opening one can close the others (an FAQ, a settings list).

Reach for Collapsible when there's a single thing to reveal.

## Controlled state

Pass `open` and `onOpenChange` to control it — useful for flipping a trigger label:

```tsx
const [open, setOpen] = React.useState(false)

<Collapsible open={open} onOpenChange={setOpen}>
  {/* … */}
  <CollapsibleTrigger asChild>
    <Button variant="link">{open ? "Show less" : "Show more"}</Button>
  </CollapsibleTrigger>
</Collapsible>
```

For an uncontrolled collapsible, pass `defaultOpen` instead.

## Rotating chevron

Put `group` on the trigger and key the icon rotation to the open state:

```tsx
<CollapsibleTrigger className="group ...">
  Section
  <ChevronDown className="transition-transform group-data-[state=open]:rotate-180" />
</CollapsibleTrigger>
```

The trigger carries `data-state="open" | "closed"`, so the chevron turns when it expands.

## Animating the height

Radix exposes the content height as `--radix-collapsible-content-height`. Add keyframes that animate from `0` to that variable to get a smooth slide:

```css
@keyframes collapsible-down { from { height: 0 } to { height: var(--radix-collapsible-content-height) } }
@keyframes collapsible-up   { from { height: var(--radix-collapsible-content-height) } to { height: 0 } }
```

Apply them on `data-[state=open]` / `data-[state=closed]`. Without animation the section just toggles, which is fine for most uses.

## Common patterns

- **Show more / less** — a read-more for long text (the Show more example).
- **Filter group** — a collapsible sidebar facet of checkboxes (the Filter group example).
- **Nested** — collapsibles inside collapsibles for a file tree (the Nested example).
- **Expandable row** — a list row that opens to show details (the Expandable row example).

## Accessibility

Radix links the trigger to the content with `aria-controls` and `aria-expanded`, and manages the open state with the keyboard (Enter/Space toggles). Keep the trigger text meaningful so the control is clear when announced.

## Installation

### collapsible

**Install:**

```bash
npx shadcn@latest add @designrevision/collapsible
```

**Dependencies:** @radix-ui/react-collapsible, utils

**Usage & accessibility:** A Radix disclosure — show/hide a single section. Compose <Collapsible> (optionally controlled with open / onOpenChange) around a <CollapsibleTrigger> and <CollapsibleContent>. Unlike Accordion (a set of items, one open at a time), Collapsible toggles one region. The content has data-state open/closed for animating height with the collapsible-down/up keyframes.

```tsx
// components/ui/collapsible.tsx
"use client"

import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"

function Collapsible({
  ...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
  return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}

function CollapsibleTrigger({
  ...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
  return (
    <CollapsiblePrimitive.CollapsibleTrigger
      data-slot="collapsible-trigger"
      {...props}
    />
  )
}

function CollapsibleContent({
  ...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
  return (
    <CollapsiblePrimitive.CollapsibleContent
      data-slot="collapsible-content"
      {...props}
    />
  )
}

export { Collapsible, CollapsibleTrigger, CollapsibleContent }
```

## Examples

### Basic

The canonical collapsible — a toggle that reveals extra items, controlled with open state.

**Install:**

```bash
npx shadcn@latest add @designrevision/collapsible-demo-01
```

**Dependencies:** lucide-react, collapsible, button

```tsx
// components/ui/collapsible-demo-01.tsx
"use client"

import * as React from "react"
import { ChevronsUpDown } from "lucide-react"

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

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

  return (
    <Collapsible
      open={open}
      onOpenChange={setOpen}
      className="flex w-full max-w-sm flex-col gap-2"
    >
      <div className="flex items-center justify-between gap-4 px-1">
        <h4 className="text-sm font-semibold">
          @peduarte starred 3 repositories
        </h4>
        <CollapsibleTrigger asChild>
          <Button variant="ghost" size="icon" className="size-8">
            <ChevronsUpDown />
            <span className="sr-only">Toggle</span>
          </Button>
        </CollapsibleTrigger>
      </div>
      <div className="rounded-md border px-4 py-2 font-mono text-sm">
        @radix-ui/primitives
      </div>
      <CollapsibleContent className="flex flex-col gap-2">
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          @radix-ui/colors
        </div>
        <div className="rounded-md border px-4 py-2 font-mono text-sm">
          @stitches/react
        </div>
      </CollapsibleContent>
    </Collapsible>
  )
}
```

---

### Chevron trigger

A full-width trigger with a chevron that rotates on open, via `group-data-[state=open]`.

**Install:**

```bash
npx shadcn@latest add @designrevision/collapsible-chevron-01
```

**Dependencies:** lucide-react, collapsible

```tsx
// components/ui/collapsible-chevron-01.tsx
import { ChevronRight } from "lucide-react"

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

export default function CollapsibleChevron01() {
  return (
    <Collapsible className="w-full max-w-sm rounded-md border">
      <CollapsibleTrigger className="group flex w-full items-center justify-between p-4 text-sm font-medium">
        What is your refund policy?
        <ChevronRight className="size-4 transition-transform group-data-[state=open]:rotate-90" />
      </CollapsibleTrigger>
      <CollapsibleContent className="text-muted-foreground px-4 pb-4 text-sm">
        We offer a 30-day money-back guarantee on all plans — no questions
        asked. Refunds are processed to your original payment method.
      </CollapsibleContent>
    </Collapsible>
  )
}
```

---

### Show more / less

A read-more pattern — the trigger label flips between Show more and Show less.

**Install:**

```bash
npx shadcn@latest add @designrevision/collapsible-text-01
```

**Dependencies:** collapsible, button

```tsx
// components/ui/collapsible-text-01.tsx
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

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

  return (
    <Collapsible open={open} onOpenChange={setOpen} className="w-full max-w-md">
      <p className="text-sm leading-relaxed">
        Embla is a bare-bones carousel library with great fluid motion and
        awesome swipe precision. It is library agnostic, dependency free, and
        100% open source.
      </p>
      <CollapsibleContent>
        <p className="mt-2 text-sm leading-relaxed">
          It ships a tiny core with optional plugins for autoplay, auto-scroll,
          class names, and more. The shadcn Carousel wraps it with styling and
          accessible controls.
        </p>
      </CollapsibleContent>
      <CollapsibleTrigger asChild>
        <Button variant="link" className="mt-1 h-auto p-0">
          {open ? "Show less" : "Show more"}
        </Button>
      </CollapsibleTrigger>
    </Collapsible>
  )
}
```

---

### Filter group

A collapsible sidebar filter — a category group of checkboxes that folds away.

**Install:**

```bash
npx shadcn@latest add @designrevision/collapsible-filter-01
```

**Dependencies:** lucide-react, collapsible, checkbox, label

```tsx
// components/ui/collapsible-filter-01.tsx
import { ChevronDown } from "lucide-react"

import { Checkbox } from "@/components/ui/checkbox"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { Label } from "@/components/ui/label"

const categories = ["Electronics", "Clothing", "Home & Garden", "Sports"]

export default function CollapsibleFilter01() {
  return (
    <div className="w-full max-w-xs">
      <Collapsible defaultOpen className="rounded-md border p-3">
        <CollapsibleTrigger className="group flex w-full items-center justify-between text-sm font-medium">
          Category
          <ChevronDown className="size-4 transition-transform group-data-[state=open]:rotate-180" />
        </CollapsibleTrigger>
        <CollapsibleContent className="mt-3 space-y-2.5">
          {categories.map((category) => (
            <div key={category} className="flex items-center gap-2">
              <Checkbox id={category} />
              <Label htmlFor={category} className="text-sm font-normal">
                {category}
              </Label>
            </div>
          ))}
        </CollapsibleContent>
      </Collapsible>
    </div>
  )
}
```

---

### Nested (file tree)

Collapsibles inside collapsibles — a folder/file tree with rotating chevrons.

**Install:**

```bash
npx shadcn@latest add @designrevision/collapsible-nested-01
```

**Dependencies:** lucide-react, collapsible

```tsx
// components/ui/collapsible-nested-01.tsx
import { ChevronRight, FileText, Folder } from "lucide-react"

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

export default function CollapsibleNested01() {
  return (
    <div className="w-full max-w-xs text-sm">
      <Collapsible defaultOpen>
        <CollapsibleTrigger className="group hover:bg-accent flex w-full items-center gap-1.5 rounded-md px-2 py-1.5">
          <ChevronRight className="size-4 transition-transform group-data-[state=open]:rotate-90" />
          <Folder className="size-4" />
          src
        </CollapsibleTrigger>
        <CollapsibleContent className="ml-4 border-l pl-2">
          <Collapsible>
            <CollapsibleTrigger className="group hover:bg-accent flex w-full items-center gap-1.5 rounded-md px-2 py-1.5">
              <ChevronRight className="size-4 transition-transform group-data-[state=open]:rotate-90" />
              <Folder className="size-4" />
              components
            </CollapsibleTrigger>
            <CollapsibleContent className="ml-4 border-l pl-2">
              <div className="flex items-center gap-1.5 px-2 py-1.5">
                <FileText className="size-4" />
                button.tsx
              </div>
              <div className="flex items-center gap-1.5 px-2 py-1.5">
                <FileText className="size-4" />
                card.tsx
              </div>
            </CollapsibleContent>
          </Collapsible>
          <div className="flex items-center gap-1.5 px-2 py-1.5">
            <FileText className="size-4" />
            index.ts
          </div>
        </CollapsibleContent>
      </Collapsible>
    </div>
  )
}
```

---

### Expandable row

A list row that expands to reveal details — the order/record disclosure pattern.

**Install:**

```bash
npx shadcn@latest add @designrevision/collapsible-details-01
```

**Dependencies:** lucide-react, collapsible, badge

```tsx
// components/ui/collapsible-details-01.tsx
import { ChevronDown } from "lucide-react"

import { Badge } from "@/components/ui/badge"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"

export default function CollapsibleDetails01() {
  return (
    <Collapsible className="w-full max-w-md rounded-lg border">
      <CollapsibleTrigger className="group flex w-full items-center justify-between p-4">
        <div className="flex items-center gap-3">
          <span className="font-medium">Order #3201</span>
          <Badge variant="secondary">Shipped</Badge>
        </div>
        <ChevronDown className="size-4 transition-transform group-data-[state=open]:rotate-180" />
      </CollapsibleTrigger>
      <CollapsibleContent className="space-y-1.5 border-t px-4 py-3 text-sm">
        <div className="flex justify-between">
          <span className="text-muted-foreground">Items</span>
          <span>3</span>
        </div>
        <div className="flex justify-between">
          <span className="text-muted-foreground">Total</span>
          <span>$148.00</span>
        </div>
        <div className="flex justify-between">
          <span className="text-muted-foreground">Tracking</span>
          <span>1Z999AA10123456784</span>
        </div>
      </CollapsibleContent>
    </Collapsible>
  )
}
```
