# Accordion

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/accordion/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

Long content in sections behind headers the user opens one or several at a time (also called collapsible sections or a disclosure). Reach for it where a reader needs a few of many sections, such as settings, a runbook or a report.

```ts
import { Accordion, AccordionItem } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/accordion/

Each header is a button inside a heading; set the heading level that fits the page's outline. A folded section keeps its content and its fields' values, hidden and inert.

## Examples

### One section at a time

By default opening one section folds the other; `defaultValue` names the sections open at the start.

```tsx
import { Accordion, AccordionItem } from "@umriss-ui/core";

export default function OneSectionAtATime() {
  return (
    <Accordion defaultValue={["goal"]} style={{ maxWidth: 520 }}>
      <AccordionItem value="goal" title="Sprint goal">
        Member portal sign-in and profile, ready for Rowan Credit Union's review on Friday.
      </AccordionItem>
      <AccordionItem value="finished" title="Finished work">
        Sign-in form, password reset and the profile page's layout: 46 of 120 hours.
      </AccordionItem>
      <AccordionItem value="carried" title="Carried over">
        Two-factor codes by text message, waiting for the provider's contract.
      </AccordionItem>
    </Accordion>
  );
}
```

### Several open, one locked

Set `type` to `multiple` for a reader who compares, `disabled` on a section that cannot open now, and `headingLevel` to fit the page.

```tsx
import { Accordion, AccordionItem, Card, CardBody, CardHeader } from "@umriss-ui/core";

export default function SeveralOpen() {
  return (
    <Card style={{ maxWidth: 560 }}>
      <CardHeader title="Checkout · runbook" />
      <CardBody>
        <Accordion type="multiple" defaultValue={["symptoms", "first-steps"]} headingLevel={4}>
          <AccordionItem value="symptoms" title="Symptoms">
            p95 latency above 300 ms, card payments failing with a timeout at the provider.
          </AccordionItem>
          <AccordionItem value="first-steps" title="First steps">
            Cap payment retries at one, then check the provider's status page.
          </AccordionItem>
          <AccordionItem value="audit" title="Audit trail" disabled>
            Only for the Payments team's leads.
          </AccordionItem>
          <AccordionItem value="escalation" title="Escalation">
            After 30 minutes page the secondary; after an hour, the head of engineering.
          </AccordionItem>
        </Accordion>
      </CardBody>
    </Card>
  );
}
```

### Open and fold all

Control `value` with `onChange` when a toolbar opens every section at once, or an address keeps which stand open.

```tsx
import { useState } from "react";
import { Accordion, AccordionItem, Button, Stack, Text } from "@umriss-ui/core";

const SECTIONS = [
  { value: "vehicle", title: "Vehicle", text: "FP 377 K, an e-van with 900 kg payload, charged to 92 %." },
  { value: "driver", title: "Driver", text: "Nadia Petrova, on the road since 07:30, break due at 11:45." },
  { value: "stops", title: "Stops", text: "10 stops, 6 delivered, 1 failed attempt, 3 to go." },
];

export default function OpenAndFoldAll() {
  const [open, setOpen] = useState<string[]>(["stops"]);

  return (
    <Stack gap={3} style={{ maxWidth: 520 }}>
      <Stack direction="row" gap={2} align="center">
        <Button size="sm" onClick={() => setOpen(SECTIONS.map((s) => s.value))}>
          Open all
        </Button>
        <Button size="sm" onClick={() => setOpen([])}>
          Fold all
        </Button>
        <Text size="xs" tone="muted">
          {open.length} of {SECTIONS.length} open
        </Text>
      </Stack>
      <Accordion type="multiple" value={open} onChange={setOpen}>
        {SECTIONS.map((s) => (
          <AccordionItem key={s.value} value={s.value} title={s.title}>
            {s.text}
          </AccordionItem>
        ))}
      </Accordion>
    </Stack>
  );
}
```

### Long settings in sections

Name what each section holds in its header; fields inside keep their own keys, and a folded section keeps its values.

```tsx
import { useState } from "react";
import {
  Accordion,
  AccordionItem,
  Badge,
  Card,
  CardBody,
  CardHeader,
  FormField,
  Input,
  NumberInput,
  Select,
  Slider,
  Stack,
  Switch,
} from "@umriss-ui/core";

export default function LongSettingsInSections() {
  const [limit, setLimit] = useState<number | null>(5000);
  const [reminder, setReminder] = useState(3);

  return (
    <Card style={{ maxWidth: 600 }}>
      <CardHeader eyebrow="Finance" title="Approval rules" actions={<Badge>Carrow & Lisle</Badge>} />
      <CardBody>
        <Accordion type="multiple" defaultValue={["thresholds"]} headingLevel={4}>
          <AccordionItem value="general" title="General - name, cost centre">
            <Stack gap={3}>
              <FormField label="Name">
                <Input defaultValue="Incoming invoices" />
              </FormField>
              <FormField label="Applies to">
                <Select defaultValue="all">
                  <option value="all">All cost centres</option>
                  <option value="CC-1200">Marketing, CC-1200</option>
                  <option value="CC-4300">IT, CC-4300</option>
                </Select>
              </FormField>
            </Stack>
          </AccordionItem>
          <AccordionItem value="thresholds" title="Thresholds - second signature, reminders">
            <Stack gap={4}>
              <FormField label="Finance signs from" hint="Net amount; below it the cost centre's owner signs alone.">
                <NumberInput value={limit} onChange={setLimit} min={0} step={500} suffix="€" />
              </FormField>
              <FormField label="Remind the approver after">
                <Slider
                  min={1}
                  max={10}
                  value={reminder}
                  onChange={setReminder}
                  format={(v) => `${v} working days`}
                  marks={[{ value: 3, label: "Default" }]}
                />
              </FormField>
            </Stack>
          </AccordionItem>
          <AccordionItem value="payment" title="Payment - run, early payment discount">
            <Stack gap={3}>
              <FormField label="Payment run">
                <Select defaultValue="tue-fri">
                  <option value="tue-fri">Tuesday and Friday</option>
                  <option value="daily">Every working day</option>
                </Select>
              </FormField>
              <Switch label="Pay early where the supplier grants a discount" defaultChecked />
            </Stack>
          </AccordionItem>
          <AccordionItem value="archive" title="Archive - retention">
            <Stack gap={3}>
              <FormField label="Keep invoices for">
                <NumberInput value={10} onChange={() => undefined} suffix="years" />
              </FormField>
              <Switch label="Archive rejected invoices as well" />
            </Stack>
          </AccordionItem>
        </Accordion>
      </CardBody>
    </Card>
  );
}
```

## When to use something else

- Alternative views of which exactly one shows → Tabs
- One block that folds on its own → Card

## Keyboard

| Key | Action |
|---|---|
| `Arrow Down / Arrow Up` | Moves to the next or previous header, wrapping and passing disabled ones. |
| `Home / End` | Moves to the first or the last header. |
| `Enter or Space` | Opens or folds the section. |
| `Tab` | Moves into an open section's content; its fields keep their own keys. |

## API

### `AccordionProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `type` | `"single" \| "multiple"` | `"single"` | `single` keeps at most one section open, `multiple` any number.<br>Shown in: [Several open, one locked](https://romanhaendler.github.io/umriss-ui/core/accordion/#several-open), [Open and fold all](https://romanhaendler.github.io/umriss-ui/core/accordion/#open-and-fold-all), [Long settings in sections](https://romanhaendler.github.io/umriss-ui/core/accordion/#long-settings-in-sections) |
| `defaultValue` | `readonly string[]` | `[]` | Uncontrolled: the sections open at the start.<br>Shown in: [One section at a time](https://romanhaendler.github.io/umriss-ui/core/accordion/#one-section-at-a-time), [Several open, one locked](https://romanhaendler.github.io/umriss-ui/core/accordion/#several-open), [Long settings in sections](https://romanhaendler.github.io/umriss-ui/core/accordion/#long-settings-in-sections) |
| `value` | `readonly string[]` | — | Controlled: the values of the open sections. A list for both types, so that switching the type changes nothing else.<br>Shown in: [Open and fold all](https://romanhaendler.github.io/umriss-ui/core/accordion/#open-and-fold-all) |
| `headingLevel` | `2 \| 3 \| 4 \| 5 \| 6` | `3` | The level of the heading each header stands in - the one that fits the page's outline.<br>Shown in: [Several open, one locked](https://romanhaendler.github.io/umriss-ui/core/accordion/#several-open), [Long settings in sections](https://romanhaendler.github.io/umriss-ui/core/accordion/#long-settings-in-sections) |
| `ref` | `Ref<HTMLDivElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

#### Events

| Prop | Type | Default | Description |
|---|---|---|---|
| `onChange` | `(value: string[]) => void` | — | Reports the open sections after a header's click. Controlled, the accordion follows once `value` does; uncontrolled it is a message.<br>Shown in: [Open and fold all](https://romanhaendler.github.io/umriss-ui/core/accordion/#open-and-fold-all) |

Also takes every attribute of `<div>` – without `defaultValue`, `onChange`.

### `AccordionItemProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `value` *required* | `string` | — | What names the section in the accordion's `value`.<br>Shown in: [One section at a time](https://romanhaendler.github.io/umriss-ui/core/accordion/#one-section-at-a-time), [Several open, one locked](https://romanhaendler.github.io/umriss-ui/core/accordion/#several-open), [Open and fold all](https://romanhaendler.github.io/umriss-ui/core/accordion/#open-and-fold-all) and 1 more |
| `title` *required* | `ReactNode` | — | The header's text. It names the section's region too.<br>Shown in: [One section at a time](https://romanhaendler.github.io/umriss-ui/core/accordion/#one-section-at-a-time), [Several open, one locked](https://romanhaendler.github.io/umriss-ui/core/accordion/#several-open), [Open and fold all](https://romanhaendler.github.io/umriss-ui/core/accordion/#open-and-fold-all) and 1 more |
| `disabled` | `boolean` | `false` | A section that cannot be opened now; its header stays in the list.<br>Shown in: [Several open, one locked](https://romanhaendler.github.io/umriss-ui/core/accordion/#several-open) |
| `ref` | `Ref<HTMLDivElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of `<div>` – without `title`.

## Known limits

- A folded section's content stays rendered; for heavy content, render it only while its section is open.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
