# Collapsible

Shows or hides one region of content from a button.

- **Import:** `import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@booleanpress/ui/collapsible"`
- **Radix Collapsible:** <https://www.radix-ui.com/primitives/docs/components/collapsible>
- **APG Disclosure:** <https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/>
- **Page:** <https://ui.booleanpress.com/components/collapsible> · @booleanpress/ui 0.1.0

## Usage

A collapsible is a disclosure: one trigger shows and hides one region. For several regions where one opens at a time, use an accordion.

```tsx
import { Button } from "@booleanpress/ui/button"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@booleanpress/ui/collapsible"

export function Advanced() {
  return (
    <Collapsible>
      <CollapsibleTrigger asChild>
        <Button variant="ghost">Advanced settings</Button>
      </CollapsibleTrigger>
      <CollapsibleContent>Port 587, STARTTLS</CollapsibleContent>
    </Collapsible>
  )
}
```

It is uncontrolled with `defaultOpen`, or controlled with `open` and `onOpenChange`. It draws nothing of its own: the trigger is a bare `button` unless you pass `asChild` and your own, and the content has no border or padding. When the content is closed it is removed from the page. `forceMount` on `CollapsibleContent` keeps it in the page while closed, still showing: it only sets `data-state="closed"`, and hiding it is yours to do (`data-[state=closed]:hidden`), for example to animate it. The content's height is exposed as `--radix-collapsible-content-height` for your own animation.

## Examples

### Basic

A button toggles two extra rows below an always-visible one.

```tsx
import { ChevronsUpDown } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@booleanpress/ui/collapsible"

export default function CollapsibleBasic() {
  return (
    <Collapsible className="flex w-full max-w-sm flex-col gap-2">
      <div className="flex items-center justify-between gap-2">
        <h4 className="text-sm font-medium">Advanced SMTP settings</h4>
        <CollapsibleTrigger asChild>
          <Button variant="ghost" size="icon" aria-label="Toggle advanced settings">
            <ChevronsUpDown />
          </Button>
        </CollapsibleTrigger>
      </div>
      <div className="rounded-md border px-3 py-2 text-sm">Host: smtp.example.com</div>
      <CollapsibleContent className="flex flex-col gap-2">
        <div className="rounded-md border px-3 py-2 text-sm">Port: 587</div>
        <div className="rounded-md border px-3 py-2 text-sm">Encryption: STARTTLS</div>
      </CollapsibleContent>
    </Collapsible>
  )
}
```

### Controlled

`open` and `onOpenChange` keep the state outside, so the trigger text can follow it.

```tsx
import { useState } from "react"
import { Button } from "@booleanpress/ui/button"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@booleanpress/ui/collapsible"

export default function CollapsibleControlled() {
  const [open, setOpen] = useState(true)

  return (
    <Collapsible open={open} onOpenChange={setOpen} className="flex w-full max-w-sm flex-col gap-2">
      <CollapsibleTrigger asChild>
        <Button variant="outline" className="justify-start">
          {open ? "Hide" : "Show"} the test email details
        </Button>
      </CollapsibleTrigger>
      <CollapsibleContent className="rounded-md border px-3 py-2 text-sm">
        Sent to ops@example.com through Amazon SES at 10:42.
      </CollapsibleContent>
    </Collapsible>
  )
}
```

### Disabled

A disabled collapsible keeps its state and ignores the trigger.

```tsx
import { Button } from "@booleanpress/ui/button"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@booleanpress/ui/collapsible"

export default function CollapsibleDisabled() {
  return (
    <Collapsible disabled className="flex w-full max-w-sm flex-col gap-2">
      <CollapsibleTrigger asChild>
        <Button variant="outline" className="justify-start">
          Routing rules
        </Button>
      </CollapsibleTrigger>
      <CollapsibleContent className="rounded-md border px-3 py-2 text-sm">Hidden while disabled.</CollapsibleContent>
    </Collapsible>
  )
}
```

## Accessibility

**Semantics.** The trigger is a `button` with `aria-expanded` and `aria-controls` pointing at the content. Both parts carry `data-state` (`open` or `closed`). Closed content is removed from the page, so `aria-controls` points at an id that exists only while it is open.

**Labels.** The trigger needs a name that stays true when the state changes ("Advanced settings"), or a changing name that says the action ("Hide details"), not both. An icon-only trigger needs an `aria-label`.

**Focus.** The trigger is a tab stop. Focus stays on it when the content opens or closes; the content's own controls follow it in the tab order.

**Known limits.**

- There is no built-in indicator: draw a chevron or change the label yourself, and do not rely on the icon alone, since the state is in `aria-expanded`.
- Closed content is not in the page, so the browser's find-in-page does not see it, and a screen reader cannot reach it.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Space | Opens or closes the content, with the trigger focused. |
| Enter | Opens or closes the content, with the trigger focused. |

## API

### Collapsible

Renders Radix Collapsible.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `defaultOpen` | `boolean` |  | Whether it starts open, when it controls itself. |
| `disabled` | `boolean` |  | Prevents the trigger from changing the state. |
| `onOpenChange` | `((open: boolean) => void)` |  | Called with the new state when the trigger is activated. |
| `open` | `boolean` |  | Whether the content is shown, when you control it. Pair it with `onOpenChange`. |

### CollapsibleTrigger

Renders Radix Collapsible.CollapsibleTrigger and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  | Render the child element, usually a `Button`, instead of a bare `button`. |

### CollapsibleContent

Renders Radix Collapsible.CollapsibleContent and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  | Render the child element instead. |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="collapsible"` (Collapsible), `data-slot="collapsible-trigger"` (CollapsibleTrigger), `data-slot="collapsible-content"` (CollapsibleContent).

## Theming
