Badges

How the Badges works with a keyboard and assistive tech, and where it stands against WCAG 2.2 AA.

FreeReact and Vue
AAWCAG 2.2 target
7/21contrast pairs passing
1keyboard interactions

A Badge is a <span> of text: not a control, not focusable, and read as part of the sentence around it. Its colour supports the label but never replaces it, so "Failed" means failed with or without the red.

Keyboard#

Tab

Passes over the badge: it is text, not a control. If it needs to do something, use a Tag or a Button.

Semantics#

<span>

Plain text, read in place.

Always
aria-hidden

Glyphs and dots are decorative; the label carries the meaning.

Dot, seal and icons
aria-label

A badge with no text needs a name, but aria-label on a <span> is not reliably read. Prefer visually hidden text inside it.

Icon only, Empty
visually hidden text

"3" alone means nothing. Add the noun for screen readers: "3 unread".

Number only

Colour contrast#

Every variant and state has to meet WCAG 2.2 Level AA. Measure the colours as they land on screen: a translucent hover or press wash over whatever sits behind it.

4.5:1

Label · Every intent and appearance

Satoshi Medium 14 is normal text, so 4.5:1 against the badge's fill. Translucent Tint washes are measured over white. Outline appearances sit on white, so they measure at or above Lighter.

WCAG 1.4.3 Contrast (Minimum) ↗
3:1

Icon only · Icon only

A seal or icon that is the whole badge needs 3:1 against its fill.

WCAG 1.4.11 Non-text Contrast ↗
4.5:1

Disabled · Disabled intent

The inactive-component exemption covers controls, and a badge is not one. A Disabled badge that says something still has to be readable; #cbcbcb on #f2f2f2 is not.

WCAG 1.4.3 Contrast (Minimum) ↗
Label

Meaning by colour · Every intent

Red, amber and green are not enough on their own. The label says the status.

WCAG 1.4.1 Use of Color ↗

Measured contrast#

Label against the badge's background for each intent in Fill, Tint and Lighter, measured on white from the badge tokens. Outline appearances use the same text colour on white. 14 of 21 pairs fall below their minimum (4.5:1 for text, 3:1 for graphics) and are marked in amber.

IntentFillTintLighter
Black · Label14.35passes12.04passes12.82passes
Neutral · Label3.23below the minimum4.75passes5.04passes
Brand · Label5.40passes3.10below the minimum4.81passes
Success · Label1.94below the minimum3.16below the minimum3.56below the minimum
Warning · Label2.27below the minimum3.22below the minimum3.86below the minimum
Error · Label4.49below the minimum3.01below the minimum4.17below the minimum
Disabled · Label1.45below the minimum1.45below the minimum1.45below the minimum

Motion#

  • Badges do not animate.

Checklist#

Write the status as a word. A coloured dot or an empty badge is not a status.

Add the noun to count badges in visually hidden text: "3 unread", "12 open issues".

Fill on Neutral, Success, Warning and Error is white on a mid tone and falls below 4.5:1 (Success at 1.9:1). Every coloured intent's Tint is near 3:1, and Success, Warning and Error stay under 4.5:1 in Lighter too. Only Black, Neutral's Tint and Lighter, and Brand's Fill and Lighter pass; use those until the tokens are darkened.

Disabled reads at about 1.5:1. Use it only where the text does not need to be read, or drop the badge.

A badge whose content changes while the page is open (a live count) is not announced. Pair it with a polite live region if the change matters.