Components/Action
Tag
Tags are small inline labels that mark or categorise whatever they sit beside. They let a reader scan a list and take in its structure at a glance, and some of them can be added or removed by the reader as a way of narrowing what they are looking at.
import { Tag } from '@growth-protocol-ai/alpha';When to use
Reach for a tag when a value belongs to something else on the row — a category, an owner, a state, a filter the reader has applied — and the reader should take it in while scanning rather than stop and read it. Let the glyph decide the rest: a tag that can be added or taken off says so with one, and a tag that only informs carries none.
When not to use
Do not use a tag as the control that carries a screen’s work; that is Button, and the way to tell them apart is what the row would lose. A tag annotates something already there and is still legible with nothing to click, where a button is the reason the row exists. A fixed set of choices the reader picks from is neither — Checkbox or Dropdown asks that question once, instead of scattering it across a row of glyphs.
variant3 values
The same three names the button uses, because a tag is a downscaled version of the same idea: how much the thing should stand out among its neighbours. What differs is the ceiling — a tag never carries a screen’s main action, so there is no depth surface and no danger.
| variant | When to use |
|---|---|
primary | The brand fill. For the one tag in a group that is the point — a suggestion to accept, a selection just made. A row of them is a row with no hierarchy in it. |
secondary | A tonal step off the page. The everyday default in practice: a category, an owner, a value out of a set, where the tag is information the reader takes in rather than acts on. |
outlined | A border and the page’s own surface. The quietest of the three, and the one to reach for on a busy row — a table cell, a card footer — where another filled shape would read as another control. |
size3 values
Geometry only, and each size pairs its height with a radius near 30% of it: a 12px arc on a 20px-tall element is half its height and reads as a lozenge rather than a tag. Regular is 36px, the height the button, the text field and the dropdown trigger share, so a tag sits inline beside any of them. There is no Large — 52px is the system’s primary-action height, and a tag is an annotation.
| size | When to use |
|---|---|
regular | 36px, 12px radius, Body Medium Bold. Beside a control, or on its own line. The only size that lines up with the rest of the system. |
small | 26px, 8px radius, the same type as Regular. Inside something that already has its own chrome — a card, a modal, a toolbar — where Regular would be the tallest thing in the box. |
xsmall | 20px, 6px radius, and the one size that steps down to Body Small Bold. Table cells, inline metric labels, dense list rows. The type change is what gives it a rank of its own rather than making it a narrower Small. |
icon3 values
The glyph is not decoration — it is what the tag does, and it decides the element. A leading + or a trailing × renders a button, so the tag is focusable, announced and clickable; neither renders a span, and a span has no cursor and no hover. So pick this by what should happen when someone clicks, and the rest follows.
| icon | When to use |
|---|---|
none | Informative, and a span. A status, a label, a piece of metadata — read-only, with nothing to click. The default, and the common case. |
add | A leading +, and a button. The item is not in the collection yet and this puts it there: a picker, a suggestion, a “create new” affordance. |
remove | A trailing ×, and a button. The tag is already applied and this takes it off. The × is the only dismissal affordance a tag has, so a removable tag must carry one. |
| State | When to use |
|---|---|
Disabled, interactive | An add or remove tag under the native attribute, so the browser drops it out of the tab order and refuses the click. The fill swaps to --surface-depth-2 and the label to --text-disabled — repainted rather than faded — and the hover step and the pointer cursor leave with it. The cursor deliberately does not become not-allowed. |
Disabled, informative | The same repaint at primary and secondary; outlined goes transparent and keeps its border instead. A span has no disabled attribute to take, so it carries aria-disabled: there is nothing to stop, and what changes is the paint and the attribute a rule or a test can read — a bare span maps to generic, so the state is there to build on rather than to be announced. Rarer than it looks, and narrower — a filter with no matching data, a deprecated value, something not available yet — rather than ordinary status display. |
Pick the icon by what a click should do, not by how the tag should look. A + adds the item to a collection, an × takes off one already applied, and no glyph means there is nothing to click. Reaching for one to balance a row hands the reader an affordance that does nothing.
Give every add and remove tag an onClick and a name a screen reader can use. The glyph is aria-hidden, so a × announces only the label beside it — “Remove Compliance” rather than the bare value.
Keep the label to a word or two. A tag never wraps, so a sentence in one stretches the row it sits in until something falls off the edge; text that has to be read rather than scanned is prose, and prose is not a tag.
An informative tag has no cursor and no hover, and that absence is the signal. If it has to be clickable it needs a glyph that says what the click does — there is no third way here, on purpose, so a tag that looks clickable is clickable.
One size per group. The sizes exist to rank a tag against what surrounds it, not against each other, so a row that mixes 36px and 20px reads as a mistake rather than as a hierarchy.
Tag carries no colour meaning yet. Success, warning and alert types are specified but not built, so a status pill that has to be green is still yours to build — do not reach for primary and hope the brand fill reads as “good”.
A disabled tag and a hovered one share a fill. --surface-depth-2 and the neutral the hover steps to are the same value in both modes, which is what the hand-off specifies, so the label colour is the only thing that separates them — do not lean on the fill alone to tell them apart.
There is no pill rounding and there will not be one here. Fully rounded reads as filter-chip UX — interactive, removable, tied to a search — and that gets its own component when the case is concrete.
Applying and removing
The two interactive positions doing the job they exist for: a value carrying a + goes on, and the same value comes back carrying a × that takes it off. Both are real buttons, so tabbing reaches every one of them — and because the glyph is aria-hidden, each names its action as well as its value.
| Prop | Type | Default | Notes |
|---|---|---|---|
variant | "primary" | "secondary" | "outlined" | "primary" | How much the tag stands out. See the variant table above. |
size | "regular" | "small" | "xsmall" | "regular" | 36px, 26px or 20px. Geometry only — never changes intent. |
icon | "none" | "add" | "remove" | "none" | Which glyph, and therefore which element: none renders a span, add and remove render a <button type="button">. Give an interactive tag an onClick. |
disabled | boolean | false | Non-interactive, and repainted rather than faded. The native attribute on an add or remove tag; aria-disabled on an informative one, which has no native equivalent. |