# Collapsible

One thing that opens, on its own.

- Group: Navigation
- Import: `import { Collapsible } from '@misoto22/folio'`
- Page: https://ui.misoto22.com/components/collapsible/
- Related: accordion

## When to reach for it

The difference from Accordion is arithmetic: an accordion is a SET and can coordinate. An accordion of one manages a value nobody reads.

## Anatomy

- **Root** (required) — Collapsible — Radix’s root, holding open or defaultOpen and drawing nothing. CollapsibleSection is the same root with the trigger and panel already composed, and is what most call sites want.
- **Trigger** (required) — A plain <button> carrying aria-expanded and aria-controls — and, unlike an accordion row, wrapped in no heading at all. Nothing here appears in a document outline.
- **Marker** (required) — A chevron rotating 180°, chosen against the accordion’s plus on purpose: this reveals more of the same thing, where an accordion row opens a distinct answer.
- **Panel** — Radix’s Content, unmounted while closed and animating on the measured --radix-collapsible-content-height, so a long group and a short one take the same time. A bare div: no region role and no name of its own, again unlike an accordion’s. Both this and the loose CollapsibleContent carry data-folio-animated, so the two agree under prefers-reduced-motion rather than only one of them honouring it.

## Best practices

### Do

- Give the trigger a heading of your own when the section is a section: Accordion wraps every trigger in an <h3> and this deliberately wraps none, so a page built from CollapsibleSections has nothing for heading navigation to stop at.
- Reach for the loose CollapsibleTrigger and CollapsibleContent only when the header has to hold more than a title — a count on one side, a switch on the other. They exist so that call site does not go to Radix and re-derive aria-expanded by hand.
- Set defaultOpen when what it hides is why the reader came: the closed panel is unmounted rather than hidden, so its text is not in the page for find-in-page, for a print, or for anything reading the rendered DOM.
- Control it with open and onOpenChange when something outside has to open it — a sidebar group that must expand for the route living inside it cannot be told to from a component that owns its own state.

### Don’t

- Do not build a set out of these: two sections cannot close each other, so the reader ends with every one open and a column to scroll past. That coordination is the whole of what Accordion’s single value buys.
- Do not flip the title between “Show more” and “Show less”: aria-expanded on the trigger already carries the state, so the row is announced with its state twice and with a new name each time it is pressed.

## Keyboard

- Enter / Space — Opens or closes it.

## Collapsible

Re-export of `CollapsiblePrimitive.Root`.

Radix Collapsible root, as a typed passthrough.

## CollapsibleTrigger

Re-export of `CollapsiblePrimitive.Trigger`.

The trigger and the panel, unstyled, for a disclosure that needs its own layout — a sidebar group whose header carries a count and a chevron on opposite sides, say. `CollapsibleSection` is the composed version and is what most call sites want; these two exist so the ones that do not have to reach for Radix directly and re-derive the keyboard and `aria-expanded` wiring. The panel animates on `--radix-collapsible-content-height`, which Radix measures — so it opens to its real height rather than to a guessed `max-height`, which is what makes a long group and a short one take the same time instead of the long one appearing to stall.

## CollapsibleContent

## CollapsibleSection

One thing that opens, on its own. The difference from `Accordion` is arithmetic: an accordion is a SET, and a set can coordinate — opening one closes another. A collapsible is one disclosure with nothing to coordinate with. Reaching for an accordion of one gets you a component managing a value you never read. The marker here is a chevron rather than the accordion's plus, and deliberately: this reveals more of the same thing, where an accordion row opens a distinct answer.

### Props

- `title` (required) — `ReactNode`. What the trigger says.
- `children` (required) — `ReactNode`.

Also accepts: `Omit<ComponentProps<typeof CollapsiblePrimitive.Root>, 'children' | 'title'>`.

## Example — default

```tsx
import { CollapsibleSection, Field, Input } from '@misoto22/folio'

<div className="w-full max-w-md divide-y divide-(--rule) border-y border-(--rule)">
  <CollapsibleSection title="Advanced settings">
    <Field label="Retries" hint="How many times a failed job is tried again.">
      <Input type="number" defaultValue={3} />
    </Field>
  </CollapsibleSection>
</div>
```

## Example — a custom header

```tsx
import { Badge, Collapsible, CollapsibleContent, CollapsibleTrigger } from '@misoto22/folio'

<Collapsible defaultOpen className="w-full max-w-md">
  <div className="flex items-center justify-between gap-4 border-b border-(--rule) py-3">
    <span className="text-sm text-(--ink)">Failed checks</span>
    <div className="flex items-center gap-3">
      <Badge tone="danger">3</Badge>
      <CollapsibleTrigger
        aria-label="Show the failed checks"
        className="group inline-flex size-8 items-center justify-center rounded-(--radius) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:text-(--ink)"
      >
        <RiArrowDownSLine
          size={16}
          aria-hidden
          className="transition-transform duration-(--duration-base) ease-(--ease-out-expo) group-data-[state=open]:rotate-180"
        />
      </CollapsibleTrigger>
    </div>
  </div>
  <CollapsibleContent>
    <ul className="m-0 flex list-none flex-col gap-2 p-0 py-3 text-sm text-(--ink-2)">
      <li>typecheck — 2 errors in apps/docs/src/lib/docs.ts</li>
      <li>lint — unused import in Toolbar.tsx</li>
      <li>visual diff — 1 changed screenshot</li>
    </ul>
  </CollapsibleContent>
</Collapsible>
```

## Example — sidebar group

```tsx
import { CollapsibleSection, NavItem } from '@misoto22/folio'

<nav className="flex w-56 flex-col gap-1" aria-label="Docs sections">
  <NavItem href="#" icon={RiHomeLine}>Overview</NavItem>
  <CollapsibleSection title="Components" open={open} onOpenChange={setOpen}>
    <div className="flex flex-col gap-1 ps-3">
      <NavItem href="#">Button</NavItem>
      <NavItem href="#" active>Pagination</NavItem>
      <NavItem href="#">Tabs</NavItem>
    </div>
  </CollapsibleSection>
</nav>
```
