Tag

User Interface (UI)

How a tag looks, from its parts and its sizes to its icon, its states and the tokens that restyle each part.

Let's start with how a tag looks: its parts, its sizes, its icon, how it shows its states and the variables that restyle each part.

Anatomy

  1. Container — The root, a <span>, which draws the background and holds the parts.
  2. Leading icon — Optional. An icon before the label, hidden from assistive technologies.
  3. Label — The text, which holds the link when the tag is one.
  4. Remove button — Optional. A <button> after the label, named after it, like "Remove Bug".
  5. Focus ring — An outline around the whole tag when its link has focus, drawn outside it so nothing moves.

Every tag has a container and a label. The leading icon renders when you give it one, and the remove button when the tag is removable, as long as it's neither disabled nor read-only. The link and the remove button sit side by side, never one inside the other, since HTML doesn't allow a button inside a link (see Don't nest interactive elements). You can see the order of the parts in the DOM contract.

Sizes

A Tag has the Input's five size names, and at each one it's as tall as the Select's tags at that size, so a tag on its own looks the same as one in a Select. We recommend size over setting a height, since it scales the text and the remove button together.

Size xsSize smSize mdSize lgSize xl
<Tag size="xs" label="Bug" removable onRemove={remove} />
<Tag size="sm" label="Bug" removable onRemove={remove} />
{/* …md, lg and xl */}
SizeHeightFont sizePadding
xs18px11px4px
sm20px12px8px
md24px12px8px
lg28px14px8px
xl32px14px8px

Heights are minimums, so a tag grows with larger text rather than clipping it. The remove button is as wide as the tag is tall, and at every size it takes a press over at least 24 × 24px, which Contrast and size explains.

Icons

A tag can have an icon before its label, like a label's symbol on an issue, which helps people scan a row of tags. The leading slot takes it, and the Tag sizes it to the text with --tag--icon--size.

BugFeatureDocs
<Tag label="Bug" leading={<BugIcon />} />
<Tag label="Feature" leading={<SparklesIcon />} />
<Tag label="Docs" leading={<BookOpenIcon />} />

Icons are decoration, so they're hidden from assistive technologies, and the label alone names the tag (WCAG 1.1.1). Keep it that way, since an icon that means something the label doesn't say is lost on anyone who can't see it. The Select's tags take their icon through a slot called icon, because it's the option's icon there, shown in the panel too. On its own, the Tag calls it leading, like the Button and the Input.

States

A plain tag has no hover or focus, since there's nothing to press. A tag that's a link, or that's removable, has hover and focus on the parts people press, and any tag can be disabled or read-only.

Bug
Plain
Bug
A link (try hovering or tabbing to it)
Bug
Removable
Bug
Disabled
Bug
Read-only
<Tag label="Bug" />
<Tag label="Bug" href="/labels/bug" />
<Tag label="Bug" removable onRemove={remove} />
<Tag label="Bug" href="/labels/bug" removable disabled />
<Tag label="Bug" href="/labels/bug" removable readonly />
StateSelectorRecommended look
Link:has(> [data-slot=label] a) on the rootA border at 55% of the text colour, which a plain tag doesn't have
Hovera[href]:hover or [data-slot=remove]:hover, inside @media (hover: hover)On a link, the background at 16% of the text colour, up from 10%, and the label underlined. On the remove button, the same background behind it
Focusa:focus-visible::after, and [data-slot=remove]:focus-visibleA 2px outline in --color--focus, 2px outside the tag for its link, and inside the remove button's edge
Disabled[data-disabled]The whole tag at 50% opacity, without its remove button
Read-only[data-readonly]The tag's usual look, without its remove button

A link tag has a border, so people can tell it from a plain tag without relying on its colour, which colour blind people may not see (WCAG 1.4.1). The border is drawn inside the tag, so a link tag is the same size as a plain one, and as the Select's tags. None of the states change the tag's size or position, so a row of tags doesn't shift as the pointer moves along it, and you'll find how they behave under Every state.

Tokens

The Tag's look comes from variables on its root, which you can set from a class. Tokens are named as token paths: --tag--… for the tag, then a group per part or state, like --tag--icon--… and --tag--link--…. They're in the components layer, so a class of yours wins without any specificity tricks.

TokenDefaultPart
--tag--height1.5rem, per sizeContainer
--tag--padding0.5rem, per sizeContainer
--tag--gap0.25remLeading icon
--tag--radius0.25remContainer
--tag--font-size0.75rem, per sizeLabel
--tag--backgroundThe text colour at 10%Container
--tag--color--color--neutral-textLabel
--tag--border-colortransparentContainer
--tag--icon--size1emLeading icon
--tag--link--border-colorThe text colour at 55%Container, as a link
--tag--hover--backgroundThe text colour at 16%Link and remove button, on hover

It reads --color--neutral-text and --color--focus from your theme. A Tag at a size has the same height, padding, radius and font size as the Select's tags at that size, and each of their tokens has a Tag token with the same job. The one default that differs is the text colour: a Select's tag inherits it from the field, while a Tag sets --color--neutral-text itself, since it isn't inside one.

The Select's tokenThe Tag's token
--select--tag--height--tag--height
--select--tag--padding--tag--padding
--select--tag--radius--tag--radius
--select--tag--font-size--tag--font-size
--select--tag--background--tag--background
--select--tag--color--tag--color
--select--tag--icon--size--tag--icon--size

The label has a contrast of at least 4.5:1 against the tag's background, which is the text colour at 10% over the page. With our reference tokens, from the Tailwind CSS palette, that's 12.3:1 on white and 10.9:1 on zinc-900. A link tag's border is the text colour at 55%, like the Input's, which comes to 3.5:1 against white and 5:1 against zinc-900, so people can see it's there (WCAG 1.4.11). The sections below restyle each part, with its variables and, where it has one, its slot.

Container and label

The root draws the tag, so its tokens set the background, the text colour and the radius. The default slot changes what the label says, and the remove button keeps its name, which comes from label.

BugFeature
<Tag className="brand" label="Bug" removable onRemove={remove} />
.brand {
  --tag--radius: 1rem;
  --tag--background: var(--color--primary);
  --tag--color: var(--color--primary-contrast);
}

If you colour tags by what they mean, like a label's colour on an issue, set --tag--background and --tag--color from a class per category, and check each pairing for 4.5:1. The colour can't be the only way people tell two tags apart, so the label still has to say it (WCAG 1.4.1).

Leading icon

The leading slot takes the icon, and --tag--icon--size and --tag--gap set its size and the space after it.

BugFeature
<Tag className="brand" label="Bug" leading={<TagIcon />} removable onRemove={remove} />
.brand {
  --tag--icon--size: 0.875rem;
  --tag--gap: 0.375rem;
}

Remove button

The remove slot changes the remove button's icon, and the button and its name stay, so a screen reader still says "Remove Bug". --tag--hover--background sets what shows behind it on hover.

BugFeature
<Tag className="brand" label="Bug" removable remove={<CircleXIcon />} onRemove={remove} />
.brand {
  --tag--hover--background: color-mix(in srgb, var(--color--error) 20%, transparent);
}

UI Checklist

RuleLevelScopeRequirementBasisCheck
tag/sizesShouldComponentIt takes the Input's five size names, each as tall as the Select's tag at that sizeOpen ComponentsUnit test, Visual regression test
tag/target-sizeMustComponentThe remove button and a link label each take a press over at least 24 × 24px, and at least 44px tall on a touch screenWCAG 2.5.8 (AA), 2.5.5 (AAA)Emulation
tag/contrastMustBothThe label has at least 4.5:1 against the tag's background, and the focus ring at least 3:1 against what's behind itWCAG 1.4.3, 1.4.11 (AA)Contrast checker
tag/focus-visibleMustComponentThe link and the remove button show an outline at least 2px thick on :focus-visibleWCAG 2.4.7 (AA)Keyboard
tag/resizableMustComponentThe tag is sized with a min-height in rem, so it grows with its textOpen Components, beyond WCAG 1.4.4 (AA)200% zoom
tag/decorative-iconsMustComponentThe leading icon and the remove button's icon are hidden from assistive technologiesWCAG 1.1.1 (A)Unit test, ARIA snapshot
tag/link-distinctShouldComponentA link tag looks different from a plain one by more than its colour, like a borderOpen Components, beyond WCAG 1.4.1 (A)Visual regression test
tag/truncateShouldComponentA long label stays on one line and ends in an ellipsis, while its full text stays in the DOMOpen ComponentsUnit test, Visual regression test
tag/stable-sizeShouldComponentHover, focus and being a link don't change the tag's size or positionOpen ComponentsVisual regression test
tag/tokensMustComponentEach part sets its look through --tag--… token paths in the components layerOpen ComponentsStylelint
tag/forced-colorsMustComponentThe tag's boundary, its focus ring and its disabled state stay visible in forced colours modeOpen ComponentsEmulation

FAQ

Copyright © 2026