Source: https://monarchui.dev/components/status-badge/

Components · Data display

# StatusBadge

Typed status registry: label, tone and a distinct icon or dot, so colour is never the only signal.

@rookcom/monarch-react

Component

## Service health

## Usage examples

### Variants

### Your own statuses

### Custom colours, contrast-guarded

## Purpose

Shows the state of a thing (a service, a request, a sync) from a typed registry that maps each status value to a human label, a semantic tone and an icon. Products add their own statuses, including workspace-defined colours, which pass through a contrast guard.

### Use when

- A value comes from an enum (`partial`, `pending_delete`) and needs a consistent label, tone and icon everywhere it appears.
- Service health: operational, degraded, partial outage, down, maintenance, unknown.
- Users or workspaces define their own statuses with colours.

### Avoid when

- A free-form tag or count with no status meaning: use Badge.
- The status must be changed in place: put a Select or DropdownMenu next to it (an editable picker is not part of StatusBadge).

## Design principles

- Colour is never the only signal: every badge has text, and the built-in statuses have distinct icon shapes (check, triangle, half-filled circle, cross, wrench, dashed question).
- One registry, one meaning: define a status once with `defineStatuses` and every screen shows it the same way.
- Never a raw enum. Unknown values render in the neutral look with a readable label ("Pending delete").
- Custom colours mark, text reads: a custom colour tints only the dot, icon and border, and is adjusted per theme until it reaches the non-text contrast target.
- Calm by default: `subtle` and `dot` for tables and lists; `solid` only for the one status that matters most on a screen.

## API reference

```tsx
import { StatusBadge, StatusRegistryProvider, builtInStatuses, checkStatusColor, defineStatuses, formatStatusLabel, resolveStatus } from '@rookcom/monarch-react';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `StatusBadge` | component | client | `.mn-status-light` |
| `StatusRegistryProvider` | component | client | `.mn-status-light` |
| `builtInStatuses` | utility | client | – |
| `checkStatusColor` | utility | client | – |
| `defineStatuses` | utility | client | – |
| `formatStatusLabel` | utility | client | – |
| `resolveStatus` | utility | client | – |

`builtInStatuses` is the shipped registry. `defineStatuses(defs, { extend? })` returns a typed registry and rejects colours that are not `#rrggbb`.

`StatusRegistryProvider` makes a registry available below it; nested providers add up. Lookup order: the `statuses` prop, the nearest provider, then the built-in set.

`resolveStatus(status, ...registries)` and `formatStatusLabel(status)` are the same lookup and label formatting, for legends and tooltips.

`checkStatusColor(hex)` is the contrast guard; it returns the per-scheme colours, the readable text colour for solid fills, and the original ratios.

Generated from the TypeScript declarations of `packages/react/src/components/data-display/status-badge`.

## Properties

`status` autocompletes the built-in keys and accepts any string from your own registry.

`StatusDefinition.description` is data for the product’s tooltip or legend; StatusBadge does not render it, because a tooltip on a non-focusable badge is not reachable by keyboard.

### `StatusBadge`

A status shown as text plus a tone and a distinct icon or dot, so colour is never the only signal. Statuses come from a typed registry that products extend with `defineStatuses` and `StatusRegistryProvider`.

| Prop | Type | Description |
| --- | --- | --- |
| `status` required | `"danger" \| "success" \| "warning" \| (string & {}) \| "neutral" \| "info" \| "down" \| "maintenance" \| "operational" \| "degraded" \| "partial" \| "unknown" \| null` | The status key: a built-in one or one from `statuses` / a StatusRegistryProvider. |
| `children` | `ReactNode` |  |
| `className` | `string` |  |
| `color` | `string` | Overrides the registry colour (`#rrggbb`), passed through the contrast guard. |
| `icon` | `ReactNode` | Replaces the registry icon. `false` hides it (the text still carries the meaning). |
| `id` | `string` |  |
| `label` | `ReactNode` | Overrides the registry label, for example with a translated string. |
| `size` | `"sm" \| "md"` |  |
| `statuses` | `Readonly<Record<string, StatusDefinition>>` | Statuses for this badge only, checked before the provider and the built-in set. |
| `variant` | `"subtle" \| "solid" \| "outline" \| "dot"` | `subtle` (default) tinted pill; `outline` for dense tables; `solid` for a single prominent status; `dot` for a quiet coloured dot plus text. |

### `StatusRegistryProvider`

Makes a product's statuses available to every StatusBadge below it. Nested providers add up.

| Prop | Type | Description |
| --- | --- | --- |
| `children` required | `ReactNode` |  |
| `statuses` required | `Readonly<Record<string, StatusDefinition>>` | Statuses added to (and overriding) the built-in set for everything inside. |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `variant` | `'subtle'``'outline'``'solid'``'dot'` | Tinted pill (default), bordered pill, filled pill, or a coloured dot with plain text. Exposed as `data-variant`. |
| `size` | `'sm'``'md'` | Height and text size. Default `md`. |
| `tone (registry)` | `'neutral'``'accent'``'info'``'success'``'warning'``'danger'` | Set per status in the registry, exposed as `data-tone`. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Known status | `data-status, data-tone` | Label, tone and icon from the registry. |
| Unknown status | `data-known="false"` | Neutral tone, dashed question icon, readable label from the raw value. |
| Custom colour | `data-custom-color` | Guarded `--mn-status-light` and `--mn-status-dark` colours mark the dot, icon and border. A custom status without an icon gets a dot, so the colour always shows. |

Not interactive: no hover, focus or disabled states.

## Interaction behaviour

Not interactive. Wrap it in a button or link when it should do something.

## Keyboard support

Not focusable; the text is read in the normal reading order.

## Accessibility

Role

none (inline text)

Accessible name

Its visible text.

- Icons and dots are `aria-hidden`; the label alone carries the meaning.
- Distinct icon shapes keep built-in statuses apart for people who cannot tell the colours apart (WCAG 1.4.1).
- Custom colours are guarded to 3:1 against the surface in light and dark, and to the more-contrast target in those themes (WCAG 1.4.11). Solid fills pick a text colour of at least 4.5:1.
- In forced-colours mode the border and dot use system colours.

## Responsive behaviour

- Never wraps; long labels truncate with an ellipsis inside a narrow cell (`max-width: 100%`).
- Same size at every viewport width.

## Theme support

- Tones use the Monarch intent tokens (`--mn-color-<tone>-subtle`, `-strong`, `-border`, `-solid`), so all themes work.
- Custom colours carry a light and a dark value; the dark one applies under `.dark` or `[data-mn-theme="dark"]`.
- The `accent` tone follows the product accent; the status tones do not.

## Do and don't

### Do

- Define each product vocabulary once with `defineStatuses` and share it through a provider.
- Translate with the registry label or the `label` prop, never by showing the enum.
- Use `dot` in dense tables and `subtle` elsewhere.

### Don't

- Do not hide the label to save space; colour alone fails people who cannot see it.
- Do not hand-pick raw palette colours for statuses; use a tone, or a guarded custom colour.
- Do not put explanations only in a `title` attribute; show them in text or a focusable tooltip trigger.

## Related components

- [Badge](https://monarchui.dev/components/badge/): Free-form labels and counts without a status meaning.
- [Tooltip](https://monarchui.dev/components/tooltip/): Explain a status from a focusable trigger next to it.
- [Alert](https://monarchui.dev/components/alert/): A status that needs a message and an action.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `@rookcom/monarch-tokens``react` |
| Monarch components | None |

Uses the tokens package’s colour maths (`contrast`, `ensureContrast`) and theme surfaces for the contrast guard; it is already a peer dependency.

## Testing coverage

- Renders label, tone and a decorative icon for a built-in status.
- Every built-in status has a label and an icon.
- Unknown and null values render a readable label in the neutral look.
- Dot variant shows a dot and text without the icon.
- Icon slot replaces or hides the icon; `label` overrides the registry label.
- Products extend the registry through a provider and the `statuses` prop; `defineStatuses` extends the built-in set and rejects malformed colours.
- Lookup order and label formatting.
- Contrast guard: keeps passing colours, adjusts failing ones to the target in all four themes, solid text at 4.5:1 or more, rejects non-hex input.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/status-badge |

Tests: `packages/react/src/components/data-display/status-badge`

## All example code

### Service health

```tsx
import { StatusBadge } from '@rookcom/monarch-react';

const health = ['operational', 'degraded', 'partial', 'down', 'maintenance', 'unknown'] as const;

{health.map((s) => (
  <StatusBadge key={s} status={s} />
))}
```

### Variants

```tsx
import { StatusBadge } from '@rookcom/monarch-react';

const health = ['operational', 'degraded', 'partial', 'down', 'maintenance', 'unknown'] as const;

const generic = ['success', 'warning', 'danger', 'info', 'neutral'] as const;

<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--mn-space-2)' }}>
  {generic.map((s) => (
    <StatusBadge key={s} status={s} variant="outline" />
  ))}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--mn-space-3)' }}>
  {health.map((s) => (
    <StatusBadge key={s} status={s} variant="dot" />
  ))}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--mn-space-2)' }}>
  <StatusBadge status="down" variant="solid" />
  <StatusBadge status="operational" variant="solid" size="sm" />
</div>
```

### Your own statuses

```tsx
import { StatusBadge, StatusRegistryProvider, defineStatuses } from '@rookcom/monarch-react';

// ClockIcon: demo icon from the docs site; use your own.

let requestCache: ReturnType<typeof buildRequestStatuses> | undefined;

const requestStatuses = () => (requestCache ??= buildRequestStatuses());

const buildRequestStatuses = () =>
  defineStatuses({
    open: { label: 'Open', tone: 'info' },
    waiting: { label: 'Waiting on requester', tone: 'warning', icon: <ClockIcon /> },
    resolved: { label: 'Resolved', tone: 'success' },
  });

<StatusRegistryProvider statuses={requestStatuses()}>
  <StatusBadge status="open" />
  <StatusBadge status="waiting" />
  <StatusBadge status="resolved" />
  <StatusBadge status="pending_delete" />
</StatusRegistryProvider>
```

### Custom colours, contrast-guarded

```tsx
import { StatusBadge, StatusRegistryProvider, defineStatuses } from '@rookcom/monarch-react';

const workspaceStatuses = defineStatuses({
  triage: { label: 'Triage', tone: 'neutral', color: '#7c3aed' },
  review: { label: 'In review', tone: 'neutral', color: '#facc15' },
  shipped: { label: 'Shipped', tone: 'neutral', color: '#0d9488' },
});

<StatusRegistryProvider statuses={workspaceStatuses}>
  <StatusBadge status="triage" variant="dot" />
  <StatusBadge status="review" variant="dot" />
  <StatusBadge status="shipped" />
  <StatusBadge status="review" variant="outline" />
  <StatusBadge status="triage" variant="solid" />
</StatusRegistryProvider>
```
