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.
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>
);
}
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>
);
}
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>
);
}
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>
);
}
| 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. |
AccordionProps| Name | Type | Default | Description |
|---|---|---|---|
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 |
defaultValue | readonly string[] | [] | Uncontrolled: the sections open at the start. Shown in: One section at a time, Several open, one locked, 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. Shown in: Open and fold all |
headingLevel | | 2 | 3 | The 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 |
ref | Ref<HTMLDivElement> | — | Receives the component's root element. from RefPropShown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more |
| Name | 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.Shown in: Open and fold all |
Also takes every attribute of <div> – without defaultValue, onChange.
AccordionItemProps| Name | Type | Default | Description |
|---|---|---|---|
valuerequired | string | — | 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 |
titlerequired | ReactNode | — | 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 |
disabled | boolean | false | A section that cannot be opened now; its header stays in the list. Shown in: Several open, one locked |
ref | Ref<HTMLDivElement> | — | Receives the component's root element. from RefPropShown 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.
What umriss deliberately does not build, and why: ADR-0032.