Skip to content

HabBadge ​

A non-interactive state label — build status, role, version state.

neutralaccentinfosuccesswarningerror
Six tones, one geometry — every tinted tone carries a transparent border
vue
<HabBadge tone="success">Passing</HabBadge>

Use HabChip instead when the thing is selectable, removable or a filter. Badge and Chip look deliberately similar and behave completely differently, so the choice is about what the user can do with it, not how it looks.

Tones ​

The five tinted tones read their --hab-<tone>-tint / -text pair. neutral is the exception on both counts. Three details are not obvious:

  • neutral fills with the --hab-neutral-tonal overlay, not a surface token. A badge does not know what it sits on, and --hab-contained is a fixed fill mixed for the page surface — on --hab-surface-2 (a panel's raised header, a footer, a banded row) it landed within two units of its background in dark mode, so the badge stopped reading as a badge. An overlay lightens or darkens whatever is under it, which is the same step on either surface. The tinted tones never had the problem: a -tint is a colour against a neutral ground, and that difference survives a surface change.
  • neutral carries a real border; the tinted tones carry a transparent one. The overlay's step is deliberately small, so fill alone is too close to its ground to carry the badge's outline on a tinted light surface. The transparent border on the other five keeps all six geometrically identical.
  • accent uses --hab-accent-text, not --hab-accent-on. -on is the accent used as text on a neutral surface. On the accent tint it would be the base color on a tint of itself, which is the exact failure the -text leg exists to prevent.

Icons ​

Passing Passing Failed Failed
An icon never changes the box — the height is pinned, not type-driven
vue
<HabBadge tone="success" :icon="mdiCheckCircle">Passing</HabBadge>

The icon is sized from --hab-icon-sm, never from the type ladder. Badge was the one component in the system sizing its glyph off type, which produced a fifth icon size in a system that documents four.

The height is pinned at --hab-control-xs, so an icon never changes the box. Left to the type it came out 23px, and an icon pushed it to 26px — a row mixing plain and icon badges sat 3px out of line.

The icon is decorative and aria-hidden. The label carries the meaning: a badge with an icon and no text is a colored box to a screen reader.

An arbitrary color ​

color overrides tone for a classifying color that comes from data — an entity kind, a session role.

PersonOrganisationLocationEventDocument
Fill, edge and text all mixed from one base color
vue
<HabBadge :color="entity.color">{{ entity.kind }}</HabBadge>

Fill, edge and text are all mixed from the one value: 14% fill, 32% edge, and text at --hab-tint-text-mix (45%) toward --hab-fg.

Pass the base color, not a pre-darkened one. The text is deliberately darkened toward the ink rather than being the base color — base-on-its-own-tint is what the -text leg exists to prevent, and it failed AA for half the hues in both product palettes. 45% is the highest ratio that clears 4.5:1 for every hue.

Passing a color drops the tone treatment entirely rather than layering over it, so the two can never disagree.

Uppercase ​

Built-in Beta Passing
uppercase is for system labels, not states

uppercase opens the tracking along with the transform — uppercase at 11px without tracking sets solid. Reach for it for built-in or system labels rather than states: a status that shouts reads as an alarm.

Code values ​

mono sets the label in the mono face, for a value that is code rather than a word — a type, an id, an environment variable's name. It replaces the sans rather than adding to it: a font-mono class passed from outside would compete with the component's own font by stylesheet order, and lose or win by accident.

Long labels ​

A badge is never wider than its container: a label that does not fit ends in an ellipsis, as HabChip's does. When the cut-off part matters, wrap the badge in a HabTooltip carrying the whole value.

Accessibility ​

  • A plain <span> with no role and no tab stop. It is a label, not a control.
  • Never let the color be the only thing carrying the meaning. The label text has to say it too — for anyone color-blind, anyone in forced-colors mode, and anyone using a screen reader.
  • Every built-in tone pair is contrast-measured. An arbitrary color is mixed to clear AA for every hue in the palette, but if you pass something far outside it, check the result.
  • If the badge communicates a change that matters as it happens, the live region belongs on the container that owns the update, not on the badge.

API ​

Props ​

PropTypeDefaultDescription
colorstringundefinedAn arbitrary classifying color, overriding tone — an entity kind, a session role. Any CSS color, var() included. The fill, edge and text are all mixed from it, and the text is darkened toward --hab-fg so any hue clears AA on its own tint. Pass the base color, not a pre-darkened one.
iconstringundefinedLeading icon as SVG path data, e.g. mdiCheck from @mdi/js. Decorative — the label is what carries the meaning.
monobooleanfalseSets the label in the mono face: for a value that is code — a type, an id, an environment variable's name — not a word. One font, never both: a mono class added from outside would compete with the component's own sans by stylesheet order.
tone"error" | "neutral" | "accent" | "info" | "success" | "warning""neutral"Semantic color role.
uppercasebooleanfalseUppercases the label and opens its tracking. For built-in or system labels rather than states.

Slots ​

SlotBindingsDescription
defaultanyThe label. A badge with no label is a colored box; give it text.
leadinganyLeading adornment, replacing the glyph. Takes precedence over icon.

Proprietary — internal use only.