---
title: Collapsible
description: Show and hide content with smooth CSS-driven animations powered by panel size CSS custom properties.
order: 4
tags: [primitives]
---

# Collapsible

A disclosure widget that toggles visibility of a content panel. It animates open and close transitions using `--collapsible-panel-height` and `--collapsible-panel-width` CSS custom properties, supports `hidden="until-found"` for browser find-in-page, and exposes open state via events and a controller.

```ts
import { Collapsible } from "@areia/slots";

document.querySelector("#root")!.innerHTML = `
  <div data-slot="collapsible" class="w-72">
    <button data-slot="collapsible-trigger" class="win95-button">
      Toggle
    </button>
    <div data-slot="collapsible-content" class="win95-inset mt-2 p-3">
      Content revealed when the trigger is activated.
    </div>
  </div>
`;

const root = document.querySelector(
  '[data-slot="collapsible"]',
)!;
const controller = Collapsible.createCollapsible(root, {
  defaultOpen: false,
});
```

## Import

```ts
import { Collapsible } from "@areia/slots";
```

## Usage

```ts
import { Collapsible } from "@areia/slots";

// Auto-bind a single root
const root = document.querySelector(
  '[data-slot="collapsible"]',
)!;
const controller = Collapsible.createCollapsible(root, {
  defaultOpen: false,
  onOpenChange: (open) => console.log("open changed:", open),
});

// Auto-bind all unbound roots in scope
const controllers = Collapsible.create();
```

## Expected Markup

The collapsible expects a trigger button and a content panel inside the root.

```html
<div data-slot="collapsible">
  <button data-slot="collapsible-trigger">Toggle</button>
  <div data-slot="collapsible-content">
    Content revealed when the trigger is activated.
  </div>
</div>
```

### Data Slots

| Slot                  | Required | Description                            |
| --------------------- | -------- | -------------------------------------- |
| `collapsible`         | Yes      | Root container.                        |
| `collapsible-trigger` | Yes      | Button that toggles the content panel. |
| `collapsible-content` | Yes      | Panel whose visibility is controlled.  |

### CSS Custom Properties

The controller sets the following CSS custom properties on the content element to enable smooth open/close animations. During transitions the values are set to the content's pixel dimensions, and reset to `auto` once the transition settles.

| Property                     | Description                                               |
| ---------------------------- | --------------------------------------------------------- |
| `--collapsible-panel-height` | Pixels during open/close transition, `auto` when settled. |
| `--collapsible-panel-width`  | Pixels during open/close transition, `auto` when settled. |

Use these variables to animate `height`, `width`, `block-size`, or `inline-size` via CSS transitions:

```css
[data-slot="collapsible-content"] {
  overflow: hidden;
  height: var(--collapsible-panel-height);
  width: var(--collapsible-panel-width);
  transition:
    height 200ms ease-out,
    width 200ms ease-out;
}
```

### Generated Attributes

| Attribute         | Element(s)    | Description                                                                        |
| ----------------- | ------------- | ---------------------------------------------------------------------------------- |
| `data-state`      | Root, content | `"open"` or `"closed"`.                                                            |
| `aria-expanded`   | Trigger       | `"true"` when open, `"false"` when closed.                                         |
| `aria-controls`   | Trigger       | Points to the content element `id`.                                                |
| `aria-labelledby` | Content       | Points to the trigger element `id`.                                                |
| `role`            | Content       | Always set to `"region"`.                                                          |
| `hidden`          | Content       | Set to `true` when closed (or `"until-found"` when `hiddenUntilFound` is enabled). |

## Events

### Outbound

| Event                | Detail              | Description                                    |
| -------------------- | ------------------- | ---------------------------------------------- |
| `collapsible:change` | `{ open: boolean }` | Fired on the root when the open state changes. |

### Inbound

| Event             | Detail              | Description                                                  |
| ----------------- | ------------------- | ------------------------------------------------------------ |
| `collapsible:set` | `{ open: boolean }` | Dispatch on the root to programmatically set the open state. |

## API Reference

### Options

| Option             | Type                      | Default | Description                                                                                             |
| ------------------ | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------- |
| `defaultOpen`      | `boolean`                 | `false` | Initial open state.                                                                                     |
| `hiddenUntilFound` | `boolean`                 | `false` | Use `hidden="until-found"` instead of `hidden` when closed, so browser find-in-page can reveal content. |
| `onOpenChange`     | `(open: boolean) => void` | —       | Called when the open state changes. Not called on initial render.                                       |

### Controller

| Member      | Type               | Description                                    |
| ----------- | ------------------ | ---------------------------------------------- |
| `open()`    | `() => void`       | Open the collapsible.                          |
| `close()`   | `() => void`       | Close the collapsible.                         |
| `toggle()`  | `() => void`       | Toggle the collapsible.                        |
| `isOpen`    | `boolean` (getter) | Current open state.                            |
| `destroy()` | `() => void`       | Remove all listeners, observers, and clean up. |

## Controller

The controller is returned by `Collapsible.createCollapsible()` and provides imperative control over the collapsible's state.

```ts
const controller = Collapsible.createCollapsible(root);

// Open programmatically
controller.open();

// Close programmatically
controller.close();

// Toggle
controller.toggle();

// Read current state
console.log(controller.isOpen); // true | false

// Clean up
controller.destroy();
```
