Tag

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

FreeReact and Vue
AAWCAG 2.2 target
10/18contrast pairs passing
4keyboard interactions

A Tag shows a value. On its own it is text in a <span>; what makes it interactive are its parts. The dismiss cross is a real <button> named "Remove {label}", and a Tag 2.0 given onToggle wraps its body in a <button> with aria-pressed. Everything else is presentation.

Keyboard#

Tab

Moves to the next interactive part: a selectable tag's body, then its dismiss button. A plain tag without either is skipped.

Shift+Tab

Moves to the previous interactive part.

Enter

On the body of a selectable tag: toggles it. On the cross: removes the tag.

Space

Same as Enter, on key release.

Semantics#

<span>

Plain text. Its leading icon, flag, logo or avatar is decorative (alt="", aria-hidden).

Static tag
<button aria-pressed>

Selected is announced as pressed. An icon-only tag gets aria-label from its label.

Tag 2.0 with onToggle
<button aria-label="Remove …">

The cross is a separate button. Without onDismiss it is drawn but hidden (aria-hidden) and does nothing.

Dismissible
aria-disabled

On the tag; its buttons are disabled and leave the tab order.

Disabled
Selected (Tag 1.0)

Visual only. Tag 1.0 has no toggle, so nothing tells assistive tech it is selected.

state="selected"

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 and sub-label · Default, Hover, Selected

Satoshi 12 is small text, so 4.5:1 in every state. Washes are measured over white.

WCAG 1.4.3 Contrast (Minimum) ↗
3:1

Dismiss cross · Dismissible

The cross is the only sign of the remove button, so it needs 3:1: #6d6d6d on white measures 5.17:1, and white on brand 5.40:1.

WCAG 1.4.11 Non-text Contrast ↗
3:1

Focus indicator · Focus visible

A 2px ring in #1a6ef8 (4.54:1 on white). Tag 2.0 draws it outside the tag with a 1px white gap; the cross rings itself.

WCAG 1.4.11 Non-text Contrast ↗WCAG 2.4.13 Focus Appearance ↗
24px

Target size · Dismiss

The cross is 12px (1.0) or 14px (2.0) but its hit area is padded to 24px and 26px.

WCAG 2.5.8 Target Size (Minimum) ↗

Measured contrast#

Label and sub-label against the tag in each state, measured on white from the tag tokens. Disabled is grey on grey and exempt as an inactive control. 8 of 18 pairs fall below their minimum (4.5:1 for text, 3:1 for graphics) and are marked in amber.

Tag · textDefaultHoverSelected
Tag 1.0 Stroke · Label5.17passes4.54passes5.40passes
Tag 1.0 Stroke · Sub-label3.23below the minimum2.84below the minimum5.17passes
Tag 1.0 Gray · Label4.75passes5.17passes5.40passes
Tag 1.0 Gray · Sub-label2.97below the minimum3.23below the minimum3.49below the minimum
Tag 2.0 · Label5.17passes12.82passes5.40passes
Tag 2.0 · Sub-label3.23below the minimum2.89below the minimum2.86below the minimum

Motion#

  • Colour changes between states take 150ms and are turned off under prefers-reduced-motion.

Checklist#

When a tag is removed, move focus to the next tag, or to the field if it was the last. The component does not; without it focus drops to the page.

Use Tag 2.0 with onToggle for filters, so selected is announced. A Tag 1.0 in its selected state only looks selected.

Sub-labels in #8f8f8f measure about 3.2:1 on white and less on a wash, and the selected 2.0 sub-label is 2.9:1. Darken them, or keep counts in the label.

Name the remove button after the value ("Remove Design"), not "Remove tag". Pass dismissAriaLabel when the label is not plain text.