# Badge

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/badge/>. 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).

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.

```ts
import { Badge } from "@umriss-ui/core";
```

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

It is never the only place a state is said: the word inside carries the meaning, and the tone repeats it for the eye.

## Examples

### A badge

A short word at the edge of what it describes; it repeats a state the text around it could also carry.

```tsx
import { Badge, Text } from "@umriss-ui/core";

export default function ABadge() {
  return (
    <Text size="sm">
      Profile page <Badge>In progress</Badge>
    </Text>
  );
}
```

### Tones

Give each state one `tone` and keep it across the application; the word inside always says the same as the colour.

```tsx
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>
  );
}
```

### Counters

`pill` is the shape for numbers: a number in it reads as a quantity. Cap long counts yourself, as in “99+”.

```tsx
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>
  );
}
```

### Mark the state of each row

In a list the badges line up in one column, so the eye runs down the states without reading every title.

```tsx
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>
  );
}
```

## When to use something else

- The user can remove it, or it stands for a chosen filter → Tag
- A figure read against limits → Stat

## Keyboard

The keys of [Button](https://romanhaendler.github.io/umriss-ui/core/button/#keyboard-button) apply here.

## API

### `BadgeProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `tone` | [`BadgeTone`](https://romanhaendler.github.io/umriss-ui/core/badge/#type-BadgeTone)<br>`"neutral" \| "accent" \| "success" \| "warning" \| "danger"` | `"neutral"` | What the badge stands for. There is always a word beside it: a colour on its own is not a statement.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/badge/#configurator-badge), [Tones](https://romanhaendler.github.io/umriss-ui/core/badge/#tones), [Counters](https://romanhaendler.github.io/umriss-ui/core/badge/#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.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/badge/#configurator-badge), [Counters](https://romanhaendler.github.io/umriss-ui/core/badge/#counters), [Fold the body (Card)](https://romanhaendler.github.io/umriss-ui/core/card/#fold-the-body) and 1 more |
| `ref` | `Ref<HTMLSpanElement>` | — | 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 `<span>`.

### Types on this page

#### `BadgeTone`

What a badge stands for - never the only statement, a word stands beside
it.

```ts
type BadgeTone = "neutral" | "accent" | "success" | "warning" | "danger";
```

## Known limits

- It cannot be clicked or removed; a label the user acts on is a [Tag](https://romanhaendler.github.io/umriss-ui/core/tag/).
- It counts nothing itself: capping at “99+” is yours.

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