Patterns · composition guide
Status badges pattern
How to build a status/state pill on a domain object — dot + localised label, the aha-status-pill class convention, the role=status + aria-label contract, i18n labels, and colour driven by the modifier class — from existing components.
◆ generated from guidelines/status-badges.json + parts/status-badges.guide.md — do not edit by hand
Based on
The rationale, worked examples, and the full assertion set live in the design skill — this pattern distils the enforceable subset and links each rule back to it.
aha-design:aha-design-status-badges
judge aha-design:aha-design-status-badges-judge
Choose the surface
| Surface | Use for | Example |
|---|---|---|
| Dashboard list / card | the lifecycle state of a survey, presentation, or collector row | draft / published / closed / archived on a survey card |
| Admin / ops table | an enum-driven state column on a domain object | active / inactive on an account row |
| Detail header | the current state of the object being viewed | a published badge beside a presentation title |
Composed of
What a compliant status badges surface reuses from this design system — the pattern's link into the component graph.
| Reuses | Kind | For | In DS? |
|---|---|---|---|
icon | component | any glyph rendered beside the status (owned by aha-design-icons) — the dot itself is decorative CSS, not a glyph | available |
--aha-color-success | token | the semantic colour a 'published'/'active' state maps to via its modifier class — never an inline hex | available |
--aha-color-error | token | the semantic colour a 'closed'/failed state maps to via its modifier class — never an inline hex | available |
status-pill | component | the StatusBadge wrapper itself (aha-status-pill + dot + label) — the canonical element this pattern describes | missing |
tag | component | the generic Tag for free-form labels this pattern explicitly is NOT (contrast target — a status badge is a fixed, semantic set, not a free-form Tag) | available |
badge | component | the generic count/notification Badge this pattern explicitly is NOT (contrast target — a status badge conveys state, not a count) | available |
Rules
The shippable checklist — each rule traces to an assertion in aha-design:aha-design-status-badges.
| Rule | Skill assertion |
|---|---|
| Render a status pill only for a value from a known status enum (e.g. SurveyStatus: draft / published / closed / archived). Never use it for free-form labels, counts, or notification dots — those are AntD Badge/Tag territory. | STATUS-01 |
| A status pill is a single inline element containing exactly two parts: a dot span (aha-status-pill__dot) carrying the state colour, followed by the localised label text. | STATUS-02 |
| Markup is the base class aha-status-pill plus a per-state modifier aha-status-pill--{status}. Use the class convention, not a one-off prop or inline style. | STATUS-02 |
| Colour of the pill and its dot is driven by the --{status} modifier class, keeping every state's colour in one stylesheet and on semantic tokens. Never set the colour inline with a hardcoded hex. | STATUS-03 |
The visible label is always sourced from i18n — t(survey_status.${status}) or the equivalent namespace for the object. Never hardcode the visible string. | STATUS-04 |
| Adding a new state means adding BOTH a translation key AND a --{status} modifier class — neither alone is complete. | STATUS-03 STATUS-04 |
| The element carries role="status" and an aria-label equal to the visible label, so the state is announced and not conveyed by colour alone. | STATUS-05 |
| State is never conveyed by the dot colour alone — the text label is always present and is the source of truth; the dot is decorative. | STATUS-06 |
| Carry a data-testid of status-badge-${status} on the pill so the rendered state is assertable. | STATUS-02 |
Composition code
Doc-only — this pattern ships no wrapper from this repo. Doc-only pattern — ships no composition wrapper from this repo. The canonical StatusBadge component and the aha-status-pill class family live in the product repo at features/dashboard/StatusBadge.tsx; that is their source of truth. If the StatusBadge wrapper graduates into this DS, add a reuse block and it is gated like a leaf/composite.
Guide
Status badges — composition guide
aha-design-status-badges plugin skill is generated FROM this pattern. The skill keeps the rationale, worked BAD/GOOD examples, and the judge verdicts / evals (aha-design-status-badges-judge); this guide is the shippable checklist. When the two ever disagree, this pattern wins and the skill is regenerated.A status pill communicates the lifecycle state of a domain object — a survey, presentation, or collector — in one glanceable, screen-reader-announced token. Its canonical form is the StatusBadge component and the aha-status-pill class family (features/dashboard/StatusBadge.tsx). This pattern adds no new control; it defines the markup, class, i18n and accessibility conventions that keep every state indicator reading as one product.
Companion skills. Use aha-design-icons for any glyph rendered beside a status,
the aha-branding:* colour skill for the semantic colour each state maps to, and aha-design-paywall for the crown badge (that is upsell, not status).
When to use a status pill
Use it to reflect a value from a known status enum (e.g. SurveyStatus: draft / published / closed / archived). Do not use it for:
- free-form labels,
- counts, or
- notification dots.
Those are AntD Badge / Tag territory, not a status pill. *(STATUS-01)*
Anatomy
A status pill is a single inline element containing exactly two parts, in order:
| Part | Class | Role |
|---|---|---|
| Dot | aha-status-pill__dot | carries the state colour; decorative only |
| Label | — (text node) | the localised label text; the source of truth |
``tsx <span className={aha-status-pill aha-status-pill--${status}} aria-label={label} role="status" data-testid={status-badge-${status}`}
<span className="aha-status-pill__dot" /> {label} </span> ```
(STATUS-02)
Class convention
- Base class
aha-status-pillplus a modifier per state:aha-status-pill--{status}. - The modifier — not an inline style or a one-off prop — controls the colour of the
pill and its dot. This keeps every state's colour in one stylesheet and on semantic tokens.
- Never set the colour inline with a hardcoded hex. *(STATUS-02, STATUS-03)*
Labels come from i18n
- The label is always
t(\survey_status.${status}\)— or the equivalent namespace for
the object. Never hardcode the visible string.
- Adding a new state means adding both a translation key and a
--{status}
modifier class. Neither alone is complete. *(STATUS-03, STATUS-04)*
Accessibility
role="status"and anaria-labelequal to the visible label, so the state is
announced and not conveyed by colour alone.
- The dot is decorative — the text label is the source of truth, never the dot
colour by itself. State is never conveyed by dot colour alone; the text label is always present. *(STATUS-05, STATUS-06)*
Build checklist
| # | Rule | Ref |
|---|---|---|
| 1 | Renders only for a known status enum — not free-form labels, counts, or notification dots | STATUS-01 |
| 2 | Markup is aha-status-pill + aha-status-pill--{status}, with a dot span and a text label | STATUS-02 |
| 3 | Colour is driven by the --{status} modifier class — no inline/hardcoded hex | STATUS-03 |
| 4 | The visible label is sourced from i18n (t('<namespace>.<status>')), never a hardcoded string | STATUS-04 |
| 5 | The element carries role="status" and an aria-label matching the visible label | STATUS-05 |
| 6 | State is not conveyed by dot colour alone — the text label is always present | STATUS-06 |
*Full rationale, screenshots, and worked examples live in the aha-design-status-badges
skill. Self-check any built pill with aha-design-status-badges-judge and fix every FAIL before shipping.*