# Collapsible

A trigger that shows and hides a panel. Unstyled, so components and applications bring their own look; for a styled one, use [Progressive disclosure](/components/progressive-disclosure.md).

```tsx
// demo.tsx
import {KeyboardArrowDownIcon} from '@earendil-works/elanor-icons-react';
import {Collapsible, Text} from '@earendil-works/elanor-react';

import styles from './collapsible.module.css';

export default function CollapsibleDemo() {
  return (
    <Collapsible.Root>
      <Collapsible.Trigger className={styles.trigger}>
        <KeyboardArrowDownIcon className={styles.chevron} />
        Recovery keys
      </Collapsible.Trigger>
      <Collapsible.Panel className={styles.panel}>
        <Text className={styles.content}>
          Store recovery keys somewhere safe. Each key works once.
        </Text>
      </Collapsible.Panel>
    </Collapsible.Root>
  );
}
```

```css
/* collapsible.module.css */
.trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--semantic-dimension-space-control-gap-small);
  padding: 0;
  border: 0;
  background: none;
  color: var(--semantic-color-text-primary);
  font: var(--semantic-typography-ui-primary);
  cursor: pointer;
}

.chevron {
  transition: rotate var(--semantic-transition-movement-disclosure);
}

.trigger:not([data-panel-open]) .chevron {
  rotate: -90deg;
}

.panel {
  height: var(--collapsible-panel-height);
  overflow: hidden;
  transition: height var(--semantic-transition-movement-disclosure);
}

.panel[data-starting-style],
.panel[data-ending-style] {
  height: 0;
}

.content {
  padding-block-start: var(--semantic-dimension-space-padding-medium);
}
```

## Usage

```tsx
import {Collapsible} from '@earendil-works/elanor-react';

<Collapsible.Root>
  <Collapsible.Trigger />
  <Collapsible.Panel />
</Collapsible.Root>;
```

`defaultOpen` starts the panel open; `open` and `onOpenChange` control it. To animate the panel, give it `height: var(--collapsible-panel-height)`, `overflow: hidden`, and a height transition, and a height of 0 with `data-starting-style` and `data-ending-style`, as the demo's stylesheet does.

## Find in page

With `hiddenUntilFound`, the browser's find in page searches a closed panel and opens it on a match. Leave the panel closed and search this page for a word in it.

```tsx
// hidden-until-found.tsx
import {KeyboardArrowDownIcon} from '@earendil-works/elanor-icons-react';
import {Collapsible, Text} from '@earendil-works/elanor-react';

import styles from './collapsible.module.css';

export default function CollapsibleHiddenUntilFoundDemo() {
  return (
    <Collapsible.Root>
      <Collapsible.Trigger className={styles.trigger}>
        <KeyboardArrowDownIcon className={styles.chevron} />
        Rollbacks
      </Collapsible.Trigger>
      <Collapsible.Panel hiddenUntilFound className={styles.panel}>
        <Text className={styles.content}>
          A deploy that fails its checks never replaces the running version.
        </Text>
      </Collapsible.Panel>
    </Collapsible.Root>
  );
}
```

```css
/* collapsible.module.css */
.trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--semantic-dimension-space-control-gap-small);
  padding: 0;
  border: 0;
  background: none;
  color: var(--semantic-color-text-primary);
  font: var(--semantic-typography-ui-primary);
  cursor: pointer;
}

.chevron {
  transition: rotate var(--semantic-transition-movement-disclosure);
}

.trigger:not([data-panel-open]) .chevron {
  rotate: -90deg;
}

.panel {
  height: var(--collapsible-panel-height);
  overflow: hidden;
  transition: height var(--semantic-transition-movement-disclosure);
}

.panel[data-starting-style],
.panel[data-ending-style] {
  height: 0;
}

.content {
  padding-block-start: var(--semantic-dimension-space-padding-medium);
}
```

## Static rendering

On pages rendered on the server without React in the browser, use the static parts. They render the same markup, and the behavior opens and closes the panel with the same states, so the same stylesheet animates it:

```tsx
<Collapsible.StaticRoot defaultOpen>
  <Collapsible.StaticTrigger />
  <Collapsible.StaticPanel hiddenUntilFound />
</Collapsible.StaticRoot>
```

```ts
import {createCollapsible, enhance} from '@earendil-works/elanor-react';

enhance({collapsible: createCollapsible});
```

Without the behavior, closed panels stay hidden.

## API

### `Collapsible.Root`

Accepts Base UI [`Collapsible.Root`](https://base-ui.com/react/components/collapsible.md) props.

### `Collapsible.Trigger`

Accepts Base UI [`Collapsible.Trigger`](https://base-ui.com/react/components/collapsible.md) props.

### `Collapsible.Panel`

Accepts Base UI [`Collapsible.Panel`](https://base-ui.com/react/components/collapsible.md) props.

### `Collapsible.StaticRoot`

A collapsible for pages without React in the browser; `createCollapsible` makes it work.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data-elanor` | `string` | - | The page's own behaviors for this element, attached after the component's. |

Also accepts Base UI [`Collapsible.Root`](https://base-ui.com/react/components/collapsible.md) props.

### `Collapsible.StaticTrigger`

Renders `<button>`. Accepts its HTML attributes and `ref`.

### `Collapsible.StaticPanel`

Accepts Base UI [`Collapsible.Panel`](https://base-ui.com/react/components/collapsible.md) props.
