A surface that holds one block of a screen together: a head with its title and actions, and a body (also called a panel or tile). Reach for it for each block of a dashboard or a detail view.
import { Card, CardHeader, CardBody } from "@umriss-ui/core";
The line under the head is not the normal case; whitespace carries the hierarchy. Set it where the body runs edge to edge, a table or a log, together with a flush body.
A collapsible card turns its head into the button for its body, with the state in the card or, controlled, in your application.
A CardHeader names the block, a CardBody holds whatever belongs to it; the card knows nothing of its content.
import { Card, CardBody, CardHeader, Text } from "@umriss-ui/core";
export default function HeadAndBody() {
return (
<Card style={{ maxWidth: 480 }}>
<CardHeader title="Checkout" />
<CardBody>
<Text size="sm" tone="secondary">
Payments team · tier 1 · 95th percentile below 300 ms, 99.95 % available a month.
</Text>
</CardBody>
</Card>
);
}
An eyebrow names the kind of thing above the title; actions stand on the right, a badge or a button or both.
import { Badge, Button, Card, CardBody, CardHeader, Stack, Text } from "@umriss-ui/core";
export default function EyebrowAndActions() {
return (
<Card style={{ maxWidth: 520 }}>
<CardHeader
eyebrow="Invoice"
title="INV-26-0318 · Brandlow Office Supply"
actions={
<Stack direction="row" gap={2} align="center">
<Badge tone="warning">Awaiting approval</Badge>
<Button size="sm" variant="primary">
Approve
</Button>
</Stack>
}
/>
<CardBody>
<Text size="sm" tone="secondary">
Facilities, CC-4400 · received 16 March, due 15 April · 2,321.98 € gross.
</Text>
</CardBody>
</Card>
);
}
Where the body runs edge to edge, like a log or a table, set flush on the body and divider on the head.
import { Card, CardBody, CardHeader } from "@umriss-ui/core";
const LOG = `09:42:10 OPENED INC-1048 by the latency alert on Checkout
09:46:02 ACKNOWLEDGED Jonas Keller
09:58:37 NOTE Card payments time out at the provider; retries doubled the load
10:11:05 MITIGATED Retries capped at one, p95 back below 300 ms`;
export default function AFlushBody() {
return (
<Card style={{ maxWidth: 640 }}>
<CardHeader divider eyebrow="INC-1048" title="Timeline" />
<CardBody flush>
<pre
style={{
margin: 0,
padding: "var(--u-space-4)",
overflowX: "auto",
fontFamily: "var(--u-font-mono)",
fontSize: "var(--u-text-xs)",
lineHeight: 1.7,
color: "var(--u-color-text-secondary)",
}}
>
{LOG}
</pre>
</CardBody>
</Card>
);
}
Set collapsible and the head becomes the button for the body; defaultCollapsed starts it folded.
import { Badge, Card, CardBody, CardHeader, Stack, Text } from "@umriss-ui/core";
const LATE = [
{ stop: "Stop 4 · Marlow & Finch Books", window: "08:00–10:00", arrival: "10:35" },
{ stop: "Stop 9 · Ashcombe Dental", window: "10:00–12:00", arrival: "12:40" },
];
export default function FoldTheBody() {
return (
<Card collapsible defaultCollapsed style={{ maxWidth: 520 }}>
<CardHeader eyebrow="Tour T-01" title="Late stops" actions={<Badge pill>{LATE.length}</Badge>} />
<CardBody>
<Stack gap={2}>
{LATE.map((late) => (
<Text key={late.stop} size="sm">
{late.stop} - window {late.window}, planned {late.arrival}
</Text>
))}
</Stack>
</CardBody>
</Card>
);
}
Control collapsed with onCollapsedChange when a toolbar folds every card at once, or an address keeps which stand open.
import { useState } from "react";
import { Button, Card, CardBody, CardHeader, Stack, Text } from "@umriss-ui/core";
const PEOPLE = [
{ id: "arjun", name: "Arjun Mehta", work: "Member portal: sign-in form, 16 h left" },
{ id: "chloe", name: "Chloe Durand", work: "Member portal: profile page, 10 h left" },
{ id: "noah", name: "Noah Fischer", work: "Online shop: search results design, 6 h left" },
];
export default function FoldFromOutside() {
const [folded, setFolded] = useState<ReadonlySet<string>>(() => new Set(["noah"]));
const fold = (id: string, collapsed: boolean) =>
setFolded((previous) => {
const next = new Set(previous);
if (collapsed) next.add(id);
else next.delete(id);
return next;
});
return (
<Stack gap={3} style={{ maxWidth: 520 }}>
<Stack direction="row" gap={2}>
<Button size="sm" onClick={() => setFolded(new Set())}>
Open all
</Button>
<Button size="sm" onClick={() => setFolded(new Set(PEOPLE.map((p) => p.id)))}>
Fold all
</Button>
</Stack>
{PEOPLE.map((person) => (
<Card
key={person.id}
collapsible
collapsed={folded.has(person.id)}
onCollapsedChange={(collapsed) => fold(person.id, collapsed)}
>
<CardHeader eyebrow="Sprint 14" title={person.name} />
<CardBody>
<Text size="sm">{person.work}</Text>
</CardBody>
</Card>
))}
</Stack>
);
}
Each block of a dashboard is a card in a grid; a card with nothing to show says so with an EmptyState.
import { Badge, Button, Card, CardBody, CardHeader, EmptyState, Grid, Stack, Text } from "@umriss-ui/core";
const INCIDENTS = [
{ id: "INC-1048", title: "Checkout slow, card payments time out", severity: "SEV1" },
{ id: "INC-1047", title: "Webhook deliveries delayed", severity: "SEV3" },
];
export default function CardsOnADashboard() {
return (
<Grid minItemWidth="240px" gap={4}>
<Card>
<CardHeader title="Open incidents" actions={<Badge tone="danger">{INCIDENTS.length}</Badge>} />
<CardBody>
<Stack gap={3}>
{INCIDENTS.map((incident) => (
<Stack key={incident.id} gap={1}>
<Text size="xs" tone="muted">
{incident.id} · {incident.severity}
</Text>
<Text size="sm">{incident.title}</Text>
</Stack>
))}
</Stack>
</CardBody>
</Card>
<Card>
<CardHeader title="On call" />
<CardBody>
<Stack gap={2}>
<Text size="sm">Primary: Jonas Keller</Text>
<Text size="sm">Secondary: Ada Mwangi</Text>
<Text size="xs" tone="muted">
Handover tomorrow at 09:00
</Text>
</Stack>
</CardBody>
</Card>
<Card>
<CardHeader title="Snoozed alerts" />
<CardBody>
<EmptyState
title="Nothing snoozed"
description="Alerts you snooze wait here until they wake."
action={<Button size="sm">Open the alert list</Button>}
/>
</CardBody>
</Card>
</Grid>
);
}
| Key | Action |
|---|---|
Tab |
Reaches the actions in the head and, in a collapsible card, its Show or Hide button; a folded body is inert and passed over. |
Enter / Space |
On Show or Hide, unfolds or folds the body. |
The keys of Button apply here.
CardProps| Name | Type | Default | Description |
|---|---|---|---|
collapsible | boolean | false | Allows collapsing and expanding the body through the header. Shown in: Fold the body, Fold from outside |
defaultCollapsed | boolean | false | Initial state of the collapse mechanism; afterwards it belongs to the
card. Only effective together with collapsible.Shown in: Fold the body |
collapsed | boolean | — | Controlled: whether the body is folded away - for a card folded from
outside, or one whose state is kept in an address. Only effective
together with collapsible.Shown in: Fold from outside |
onCollapsedChange | (collapsed: boolean) => void | — | Reports the wish of the header's button. Controlled, the card folds only
once collapsed follows; uncontrolled it is merely a message.Shown in: Fold from outside |
ref | Ref<HTMLElement> | — | 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 the rendered element.
CardHeaderProps| Name | Type | Default | Description |
|---|---|---|---|
titlerequired | ReactNode | — | The heading of the card. A node and not merely text, so that an
identifier with markup can stand in it too. Shown in: Head and body, Eyebrow and actions, A flush body and 27 more |
eyebrow | ReactNode | — | Small all-caps label above the title, e.g. a category. Shown in: Eyebrow and actions, A flush body, Fold the body and 17 more |
actions | ReactNode | — | Right-aligned area, e.g. buttons or badges. Shown in: Eyebrow and actions, Fold the body, Cards on a dashboard and 13 more |
divider | boolean | false | A dividing line below the head. Only set it where the body needs it -
typically with flush data (flush tables, logs). With normal content the
white space carries the hierarchy on its own. Shown in: A flush body |
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.
CardBodyProps| Name | Type | Default | Description |
|---|---|---|---|
flush | boolean | false | Removes the inner padding, e.g. for flush tables. Shown in: A flush body |
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>.