A small word or number at the edge of something else: a status, a count (also called a label or chip). Reach for it to mark the state of a row, or how many items wait behind a button.
import { Badge } from "@umriss-ui/core";
It is never the only place a state is said: the word inside carries the meaning, and the tone repeats it for the eye.
A short word at the edge of what it describes; it repeats a state the text around it could also carry.
import { Badge, Text } from "@umriss-ui/core";
export default function ABadge() {
return (
<Text size="sm">
Profile page <Badge>In progress</Badge>
</Text>
);
}
Give each state one tone and keep it across the application; the word inside always says the same as the colour.
import { Badge, Stack } from "@umriss-ui/core";
export default function Tones() {
return (
<Stack direction="row" gap={2} wrap align="center">
<Badge>To do</Badge>
<Badge tone="accent">In progress</Badge>
<Badge tone="warning">In review</Badge>
<Badge tone="success">Done</Badge>
<Badge tone="danger">Blocked</Badge>
</Stack>
);
}
pill is the shape for numbers: a number in it reads as a quantity. Cap long counts yourself, as in “99+”.
import { Badge, Button, Stack } from "@umriss-ui/core";
function capped(count: number) {
return count > 99 ? "99+" : String(count);
}
export default function Counters() {
return (
<Stack direction="row" gap={3} wrap align="center">
<Button size="sm">
Open incidents <Badge pill tone="danger">{capped(2)}</Badge>
</Button>
<Button size="sm">
Alerts <Badge pill tone="accent">{capped(7)}</Badge>
</Button>
<Button size="sm">
Resolved this month <Badge pill>{capped(148)}</Badge>
</Button>
</Stack>
);
}
In a list the badges line up in one column, so the eye runs down the states without reading every title.
import { Badge, Stack, Text } from "@umriss-ui/core";
import type { BadgeTone } from "@umriss-ui/core";
/* Data from the planning world, written out here so the example runs on its own. */
const at = (day: number, hours = 9, minutes = 0) => new Date(2026, 2, day, hours, minutes).getTime();
/** A work item, shaped as `@umriss-ui/schedule`'s `Subtask`. */
interface WorkItem {
id: string;
/** The project. */
task: string;
/** The person. */
lane: string;
from: number;
to: number;
name: string;
sprint: string;
/** Hours, as estimated at planning. */
estimate: number;
status: "to do" | "in progress" | "in review" | "done";
}
const item = (id: string, task: string, lane: string, from: number, to: number, name: string, estimate: number, status: WorkItem["status"]): WorkItem =>
({ id, task, lane, from, to, name, sprint: "sprint-14", estimate, status });
const WORK: readonly WorkItem[] = [
item("w-101", "portal", "arjun", at(9), at(11, 17), "Sign-in with e-mail code", 18, "done"),
item("w-102", "portal", "arjun", at(12), at(17, 17), "Profile page", 26, "in progress"),
item("w-103", "portal", "chloe", at(9), at(10, 17), "Session handling", 12, "done"),
item("w-104", "shop", "chloe", at(11), at(13, 17), "Basket keeps items across devices", 20, "in review"),
item("w-105", "portal", "chloe", at(16), at(19, 17), "Change of address form", 24, "in progress"),
item("w-106", "portal", "noah", at(9), at(12, 13), "Profile page design", 14, "done"),
item("w-107", "shop", "noah", at(16), at(18, 17), "Search results layout", 12, "to do"),
item("w-108", "portal", "eva", at(12), at(13, 17), "Test plan for sign-in", 10, "done"),
item("w-109", "portal", "eva", at(17), at(20, 17), "Regression run", 20, "to do"),
item("w-110", "booking", "hana", at(9), at(13, 17), "Reminder scheduling service", 32, "done"),
item("w-111", "booking", "hana", at(16), at(20, 17), "Push notifications", 30, "in progress"),
item("w-112", "booking", "kofi", at(10), at(12, 17), "Calendar sync", 18, "done"),
item("w-113", "intranet", "kofi", at(16), at(18, 17), "News feed", 16, "in progress"),
item("w-114", "booking", "freya", at(9), at(11, 17), "Reminder settings screen", 16, "done"),
item("w-115", "booking", "freya", at(18), at(20, 17), "Store screenshots", 12, "to do"),
item("w-116", "booking", "david", at(16), at(19, 13), "Device test matrix", 14, "to do"),
];
const TONE: Record<WorkItem["status"], BadgeTone> = {
"to do": "neutral",
"in progress": "accent",
"in review": "warning",
done: "success",
};
export default function InAList() {
return (
<Stack gap={2} style={{ maxWidth: 520 }}>
{WORK.filter((item) => item.task === "portal").map((item) => (
<Stack key={item.id} direction="row" gap={3} align="center">
<Text as="span" size="sm" mono tone="muted">
{item.id}
</Text>
<Text as="span" size="sm" style={{ flex: 1 }}>
{item.name}
</Text>
<Badge tone={TONE[item.status]}>{item.status}</Badge>
</Stack>
))}
</Stack>
);
}
The keys of Button apply here.
BadgeProps| Name | Type | Default | Description |
|---|---|---|---|
tone | BadgeTone| "neutral" | "neutral" | What the badge stands for. There is always a word beside it: a colour on
its own is not a statement. Shown in: Configurator, Tones, Counters and 19 more |
pill | boolean | false | Fully rounded pill shape - for counters and key figures. The default is
the slightly rounded badge shape for status labels. Shown in: Configurator, Counters, Fold the body (Card) and 1 more |
ref | Ref<HTMLSpanElement> | — | 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 <span>.
BadgeToneWhat a badge stands for - never the only statement, a word stands beside it.
type BadgeTone = "neutral" | "accent" | "success" | "warning" | "danger";What umriss deliberately does not build, and why: ADR-0032.