Skip to content

Badge

A small pill marking a status, a label or a presence, rendered as <span is="root-badge">.

Usage

Active
Active
<span is="root-badge" data-color="success">Active</span>

data-variant and data-color work the way they do on Button: the variant is the shape, the color is the palette. Unlike a Button, a badge with neither is not a bare outline - it is the Default palette in its default shape, because a badge is a filled mark by definition.

New
New
<span is="root-badge">New</span>

Variants

Two shapes. default carries a short label - a word, an icon, occasionally a count - and dot carries nothing: it is empty by definition, half the badge's own height at every size, and it marks a state on something else rather than saying anything of its own.

An empty element has no accessible name, so a dot needs aria-label - and role="img" rather than the component's default status, because a state that is drawn once is not a live region.

Active
Active
<span is="root-badge" data-color="success">Active</span>
<span is="root-badge" data-variant="dot" data-color="success" role="img" aria-label="Online"></span>
<span is="root-badge" data-variant="dot" data-color="neutral" role="img" aria-label="Offline"></span>

Palettes

data-color names a ramp: default, alternate, info, success, warning, danger and neutral. The value painted is that ramp's -400 step, and the label is a contrast-color() over it - which is what makes the same seven names work in both schemes, where the -400 steps sit at opposite ends of their ramps.

Default Alternate Info Success Warning Danger Neutral
Default Alternate Info Success Warning Danger Neutral
<span is="root-badge" data-color="default">Default</span>
<span is="root-badge" data-color="alternate">Alternate</span>
<span is="root-badge" data-color="info">Info</span>
<span is="root-badge" data-color="success">Success</span>
<span is="root-badge" data-color="warning">Warning</span>
<span is="root-badge" data-color="danger">Danger</span>
<span is="root-badge" data-color="neutral">Neutral</span>

Every palette takes the dot:

<span is="root-badge" data-variant="dot" data-color="default" role="img" aria-label="Default"></span>
<span is="root-badge" data-variant="dot" data-color="alternate" role="img" aria-label="Alternate"></span>
<span is="root-badge" data-variant="dot" data-color="info" role="img" aria-label="Info"></span>
<span is="root-badge" data-variant="dot" data-color="success" role="img" aria-label="Online"></span>
<span is="root-badge" data-variant="dot" data-color="warning" role="img" aria-label="Away"></span>
<span is="root-badge" data-variant="dot" data-color="danger" role="img" aria-label="Busy"></span>
<span is="root-badge" data-variant="dot" data-color="neutral" role="img" aria-label="Offline"></span>

Sizes

data-size takes small, medium and large; medium is the default and needs no value written. Measured heights are 31.25px for large and 25px for both medium and small - the two are the same height on purpose, and only the type changes between them (16 / 14.2 / 12.6px). Large also takes the wider inline padding. The dot follows at half the height: 15.63 / 12.5 / 12.5px.

Large Medium Small
Large Medium Small
<span is="root-badge" data-color="default" data-size="large">Large</span>
<span is="root-badge" data-color="default">Medium</span>
<span is="root-badge" data-color="default" data-size="small">Small</span>
<span is="root-badge" data-variant="dot" data-color="danger" data-size="large" role="img" aria-label="Busy"></span>
<span is="root-badge" data-variant="dot" data-color="danger" role="img" aria-label="Busy"></span>
<span is="root-badge" data-variant="dot" data-color="danger" data-size="small" role="img" aria-label="Busy"></span>

Modifiers

data-mask="true" adds a 2px ring in the page ground, so a badge sitting on top of something else reads as separate from it rather than as part of it. The border box exists at every variant, so switching the mask on does not change the badge's height.

The attribute is always in the DOM: the component writes data-mask="false" when the markup does not, so what a badge is reads out of the element rather than out of an attribute that is not there.

Overdue
Overdue
<span is="root-badge" data-color="danger" data-mask="true">Overdue</span>
<span is="root-badge" data-variant="dot" data-color="success" data-mask="true" role="img" aria-label="Online"></span>

Anchor points

data-anchor pins the badge to a corner of the thing it marks, and the four read clockwise from the top-left: top-left, top-right, bottom-right, bottom-left. The fifth case in the design is inline - that is a badge with no data-anchor, in flow beside its label.

An anchored badge is absolutely positioned, so the host has to be the positioned ancestor: mark it data-badge-host. It sits in the host's corner rather than outside it, which on a round host is where the circle's edge runs - and that overlap is what data-mask="true" is for: the ring separates the mark from the surface under it.

All four corners, with a dot on each:

AL GH AT KJ
AL GH AT KJ
<span data-badge-host>
  <span is="root-avatar" aria-label="Ada Lovelace">AL</span>
  <span is="root-badge" data-variant="dot" data-color="warning" data-anchor="top-left" data-mask="true" role="img" aria-label="Away"></span>
</span>
<span data-badge-host>
  <span is="root-avatar" aria-label="Grace Hopper">GH</span>
  <span is="root-badge" data-variant="dot" data-color="success" data-anchor="top-right" data-mask="true" role="img" aria-label="Online"></span>
</span>
<span data-badge-host>
  <span is="root-avatar" aria-label="Alan Turing">AT</span>
  <span is="root-badge" data-variant="dot" data-color="danger" data-anchor="bottom-right" data-mask="true" role="img" aria-label="Busy"></span>
</span>
<span data-badge-host>
  <span is="root-avatar" aria-label="Katherine Johnson">KJ</span>
  <span is="root-badge" data-variant="dot" data-color="neutral" data-anchor="bottom-left" data-mask="true" role="img" aria-label="Offline"></span>
</span>

Cases

The five the design calls out, in the order it calls them:

A dot for presence - online, offline, "there is something new". It is the least intrusive form and it is empty: no number, no word, nothing to read. Its meaning comes from where it sits and from its aria-label.

A count when the quantity really is the information, which is the exception rather than the rule - a badge normally carries a word. Past two digits, cap it: a badge that keeps growing shifts the host's layout.

99+
99+
<span is="root-badge" data-color="danger" data-size="small">99+</span>

A status label - a short word instead of a number, for outcomes rather than counts. Keep it to one word.

Done
Done
<span is="root-badge" data-color="success">Done</span>

With a leading icon, when the color alone would be ambiguous: the icon carries the meaning and the label qualifies it.

Pending
Pending
<span is="root-badge" data-color="warning"><i is="root-icon" data-icon="clock"></i>Pending</span>

A neutral count for a count with no urgency. Reserve danger for what actually needs attention - a red badge on every item stops signalling anything.

12
12
<span is="root-badge" data-color="neutral" data-size="small">12</span>

Attributes

Attribute Type Default Description
data-variant 'default' | 'dot' 'default' Shape: a labelled pill, or an empty dot half its height
data-color 'default' | 'alternate' | 'info' | 'success' | 'warning' | 'danger' | 'neutral' 'default' Which ramp supplies the surface
data-size 'small' | 'medium' | 'large' 'medium' Height, inline padding and type step
data-mask 'true' | 'false' 'false' 2px ring in the page ground; written by the component when absent
data-anchor 'top-left' | 'top-right' | 'bottom-right' | 'bottom-left' - Pins the badge to a corner of a [data-badge-host], clockwise from the top-left
role string 'status' Set by the component when absent
aria-label string - Accessible name; required on a dot, which has no content

Extends

HTMLSpanElement - use with <span is="root-badge">.