Accordion

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.

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

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.

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.

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.

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.

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

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

NameTypeDefaultDescription
type"single" | "multiple""single"single keeps at most one section open, multiple any number.

Shown in: Several open, one locked, Open and fold all, Long settings in sections

defaultValuereadonly string[][]Uncontrolled: the sections open at the start.

Shown in: One section at a time, Several open, one locked, Long settings in sections

valuereadonly string[]—Controlled: the values of the open sections. A list for both types, so that switching the type changes nothing else.

Shown in: Open and fold all

headingLevel| 2
| 3
| 4
| 5
| 6
3The level of the heading each header stands in - the one that fits the page's outline.

Shown in: Several open, one locked, Long settings in sections

refRef<HTMLDivElement>—Receives the component's root element. from RefProp

Shown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more

Events

NameTypeDefaultDescription
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.

Shown in: Open and fold all

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

AccordionItemProps

NameTypeDefaultDescription
valuerequiredstring—What names the section in the accordion's value.

Shown in: One section at a time, Several open, one locked, Open and fold all and 1 more

titlerequiredReactNode—The header's text. It names the section's region too.

Shown in: One section at a time, Several open, one locked, Open and fold all and 1 more

disabledbooleanfalseA section that cannot be opened now; its header stays in the list.

Shown in: Several open, one locked

refRef<HTMLDivElement>—Receives the component's root element. from RefProp

Shown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more

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

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/core

Getting started

Customising

Layout

Typography

Actions

Forms

Choosing

Dates and times

Feedback

Overlays

Navigation

Data display

API index