---
title: Collapsible
description: A disclosure that shows and hides a single section of content.
---

<PackageInfo name="collapsible" />

**bun**

```bash
bun add @data-slot/collapsible
```

**npm**

```bash
npm install @data-slot/collapsible
```

**pnpm**

```bash
pnpm add @data-slot/collapsible
```

## Anatomy

Both parts are required: a `collapsible-trigger` to toggle, and a `collapsible-content` to reveal.

```html
<div data-slot="collapsible">
  <button data-slot="collapsible-trigger">Toggle</button>
  <div data-slot="collapsible-content">Content</div>
</div>
```

## Examples

### Show and hide a section

<Example name="collapsible" />

### Open by default

Start expanded with `data-default-open`.

<Example name="collapsible" variant="extra" />

## API reference

### Initialization

#### `create(scope?)`

Auto-discover and bind all collapsible instances in a scope (defaults to `document`).

```typescript
import { create } from "@data-slot/collapsible";

const controllers = create(); // Returns CollapsibleController[]
```

#### `createCollapsible(root, options?)`

Create a controller for a specific element.

```typescript
import { createCollapsible } from "@data-slot/collapsible";

const collapsible = createCollapsible(element, {
  defaultOpen: false,
  hiddenUntilFound: false,
  onOpenChange: (open) => console.log(open),
});
```

### Slots

#### Runtime Slots

- `collapsible` - Root element that manages the open state.
- `collapsible-trigger` - Required button that toggles the content and receives `aria-expanded`.
- `collapsible-content` - Required panel that is shown or hidden with the open state.

#### Markup

```html
<div data-slot="collapsible">
  <button data-slot="collapsible-trigger">Toggle</button>
  <div data-slot="collapsible-content">Content</div>
</div>
```

### Data Attributes

Options can also be set via data attributes on the root element. JS options take precedence.

| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `data-default-open` | boolean | `false` | Initial open state |
| `data-hidden-until-found` | boolean | `false` | Use `hidden="until-found"` when closed |

Boolean attributes: present or `"true"` = true, `"false"` = false, absent = default.

```html
<!-- Start expanded -->
<div data-slot="collapsible" data-default-open>
  ...
</div>
```

### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `defaultOpen` | `boolean` | `false` | Initial open state |
| `hiddenUntilFound` | `boolean` | `false` | Use `hidden="until-found"` when closed |
| `onOpenChange` | `(open: boolean) => void` | `undefined` | Callback when open state changes (not called on init) |

### Controller

| Method/Property | Description |
|-----------------|-------------|
| `open()` | Open the collapsible |
| `close()` | Close the collapsible |
| `toggle()` | Toggle the collapsible |
| `isOpen` | Current open state (readonly `boolean`) |
| `destroy()` | Cleanup all event listeners |

### Events

#### Outbound Events

Listen for changes via custom events:

```javascript
element.addEventListener("collapsible:change", (e) => {
  console.log("Collapsible open:", e.detail.open);
});
```

#### Inbound Events

Control the collapsible via events:

| Event | Detail | Description |
|-------|--------|-------------|
| `collapsible:set` | `{ open: boolean }` | Set open state programmatically |

```javascript
// Open the collapsible
element.dispatchEvent(
  new CustomEvent("collapsible:set", { detail: { open: true } })
);

// Close the collapsible
element.dispatchEvent(
  new CustomEvent("collapsible:set", { detail: { open: false } })
);
```

**Note:** Blocked when trigger is disabled (has `disabled` attribute or `aria-disabled="true"`).

##### Deprecated Shapes

The following shape is deprecated and will be removed in v1.0:

```javascript
// Deprecated: { value: boolean }
element.dispatchEvent(
  new CustomEvent("collapsible:set", { detail: { value: true } })
);
```

Use `{ open: boolean }` instead.

### Styling

Use `data-state` attributes for CSS styling (available on both root and content):

```css
/* Hidden state */
[data-slot="collapsible-content"][hidden] {
  display: none;
}

/* Or use data-state */
[data-slot="collapsible"][data-state="closed"] [data-slot="collapsible-content"] {
  display: none;
}

/* Animate using data-state on content */
[data-slot="collapsible-content"] {
  overflow: hidden;
  transition: max-height 0.3s;
  max-height: 0;
}

[data-slot="collapsible-content"][data-state="open"] {
  max-height: 500px;
}

/* Presence lifecycle markers */
[data-slot="collapsible-content"][data-starting-style] {
  opacity: 0;
}

[data-slot="collapsible-content"][data-ending-style] {
  opacity: 0;
}
```

#### CSS Variables

The content element exposes size variables you can use for dimension animations:

| Variable | Description |
|----------|-------------|
| `--collapsible-panel-height` | Panel height (`auto` at open rest, measured px during transitions, `0px` when closed) |
| `--collapsible-panel-width` | Panel width (`auto` at open rest, measured px during transitions, `0px` when closed) |

Example:

```css
[data-slot="collapsible-content"] {
  overflow: hidden;
  height: var(--collapsible-panel-height);
  width: var(--collapsible-panel-width);
  transition: height 0.25s ease, width 0.25s ease, opacity 0.2s ease;
}

[data-slot="collapsible-content"][data-starting-style],
[data-slot="collapsible-content"][data-ending-style] {
  height: 0;
  opacity: 0;
}
```

### Accessibility

The component automatically handles:

- `aria-expanded` state on trigger
- `aria-controls` linking trigger to content
- `role="region"` on content
- `aria-labelledby` linking content back to trigger
- Unique ID generation for trigger and content
- Disabled trigger support (respects `disabled` attribute and `aria-disabled="true"`)

### Behavior

#### Find-in-Page Support

Enable `hiddenUntilFound` (or `data-hidden-until-found`) to close panels with
`hidden="until-found"`. This allows browser find-in-page to reveal matching text.

With Tailwind:

```html
<div data-slot="collapsible" class="group">
  <button data-slot="collapsible-trigger" class="flex items-center gap-2">
    <span>Show more</span>
    <svg class="group-data-[state=open]:rotate-180 transition-transform">...</svg>
  </button>
  <div data-slot="collapsible-content" class="hidden group-data-[state=open]:block">
    Content here
  </div>
</div>
```
