Pill

The small rounded label that carries an attribute of a record: a tag, a state, a category. Primitive .acrm-pill in apps/web/src/ds.css — there is no React component of its own; TagPills, StatusChip and the listing columns all emit these classes.

Colour is carried by a saturated dot, not by a tinted ground (AD 6 Sep 2026, after Linear). The label is an outlined chip with no fill, the text runs at full contrast, and a tone sets exactly one thing: the colour of the dot.

The tinted pill went because it was barely working. Its ground measured 1.03–1.19:1 against the canvas (grey 1.03 · amber 1.05 · purple 1.06 · blue 1.08 · rose 1.08 · lime 1.19), so in dark theme the tone did almost nothing and the colour was really carried by the text. A dot can be fully saturated and the text can go to full contrast, which makes the label more legible, not merely quieter. It also removed two special cases: grey had no shape at all in dark, and every tone needed a background and an ink instead of one colour.

  • the palette — lime · amber · blue · purple · green · rose · grey: seven hues chosen so neighbouring labels look different. A screen may give a hue a local meaning (StatusChip uses green for stage won and status client because green reads as "achieved"), and that is fine — it just is not a property of the tone.
  • the reserved word — critical: red means something is wrong, and nothing else may claim it.

The DS used to describe two families of tone — arbitrary attribute tones versus functional tones whose colour is the message. That model was dropped on 5 Sep 2026 because it did not survive being checked: positive, warning and info held byte-for-byte the values of green, amber and blue (with 0, 0 and 1 call sites between them), pill accent held blue's ink, a reader cannot perceive which family a colour belongs to, and the app already broke the rule on purpose.

AD standing note: pills are over-used. A new pill needs a reason; where a value is not an attribute of the record, plain text is the better answer.

When to use

  • Tags and categories on a record (attribute tones).
  • A state the user reads at a glance in a dense row (functional tones, optionally with .dot).
  • A removable token in a tag input or a picker (.x).

When not to use

  • A count — that is count-badge.
  • A phase on a record card: the phase pill lives in tables only; the card shows a tile (F134–F136).
  • A value that is not an attribute of the record — write it as text.
  • A label for a control — that is the control's own label.

Anatomy

  • span.acrm-pill.<tone>; add .sm, and a button.x for removable.
  • The dot is a ::before: 8px, var(--pill-dot), set by the tone class. A label with a leading icon has no dot (:has(> .ti) — a direct child only, so the remove cross .x .ti does not take the dot away) — two marks left of the text is one too many. The icon carries the tone instead of the dot: .acrm-pill > .ti { color: var(--pill-dot, var(--ic-grey)) } (owner 8 Oct 2026, acrm !4915, AD FA258-re no. 5). It is a general rule for every pill with a leading icon; first consumer is project-label.
  • Frame 1px solid var(--border), no fill, text var(--fg).
  • Geometry: height 22px, padding 0 10px 0 8px (tighter on the dot side, because a dot is narrower than a glyph); .sm is 20px / 0 8px 0 7px.
  • .dot is now a no-op — every label has a dot. The class is kept so old call sites do not break; do not add it to new ones.

With icon — the whole Tabler set

A leading icon does not have to come from the DS <Icon> allowlist. TablerIcon (apps/web/src/components/TablerIcon.tsx, owner 27 Sep 2026, acrm !4004 — meeting topic icons picked by the model) draws from the full outline set (5,130 glyphs) instead: it lazy-fetches /tabler/<name>.svg on first render, caches it in memory, inlines the markup so it inherits currentColor/1em like a DS icon, and rewrites the stroke to 1.75 to match <Icon> (Tabler ships 2). The wrapper carries .ti either way, so the pill's :has(.ti) no-dot rule applies the moment the slot mounts, before the SVG itself has loaded. An unknown or failed name falls back to the DS icon tag, never an empty slot. The DS <Icon> allowlist itself is unchanged — this is an app-level escape hatch for one AI-picked case, not a second icon system; reach for <Icon> first and only drop to TablerIcon where the label names something the closed allowlist cannot (a topic, not a fixed piece of UI chrome).

Composition — "section tags" (add/remove in a section header)

A row of removable pills with a quiet "Add" action next to the section title (meeting Minutes, "Probraná témata" — TopicsBlock in SummaryTab.tsx, acrm !4013): plain .acrm-pill tokens with button.x, no frame around the row — a boxed tag input is a form control and belongs in a form, not a read section. Adding opens a Popover anchored to the section's quiet text action (SectionBlock action) holding one canonical Input; Enter commits and reopens an empty field, Escape discards, closing the popover commits whatever was typed. The new topic keeps TablerIcon's tag fallback until the model assigns a real one.

There is deliberately no exception for tables. Dropping the frame in a cell was tried — the column is calmer — but the rule needed two conditions (only in a table, and only when the label is alone in the cell, since two frameless labels side by side merge) and the second depends on data: a record with one tag would render differently from one with two, in the same column. A label looks the same everywhere.

Best practices

Do

  • Pick palette hues for distinctness from their neighbours.
  • Use .sm inside table rows and other dense lines, where the default would push the row height — the task list row does this for its tags, tone picked from the palette by tag name.
  • Give the .x an aria-label naming what it removes (Odebrat VIP).
  • Put the pills that label a list row into the tags slot of Item list, not into a column.
  • Keep critical for things that are actually wrong, so red stays trustworthy.

Don't

  • Bring positive, warning, info or a pill accent back. They are gone on purpose; a palette hue is the answer.
  • Add a new tone.
  • Use a pill where text would do (AD: the planned reduction is page by page).
  • Bring back a tinted background. The colour is the dot; a fill would fight the text and reintroduce the grey-in-dark problem.

Content

  • One or two words, sentence case, no full stop.
  • The tag as the user typed it; the state in the domain's own vocabulary.
  • count-badge — counts, not attributes.
  • project-label — a pill with the project’s icon, the icon carries the tone.
  • item-list — label pills of a row always go to its tags slot.
  • entity-ref (variant removable) — a removable reference to a record, which is a different thing from a tag.
  • deal-card, table — the usual hosts.
  • popover, text-field — the add-topic composition's Popover + Input.
  • Stories: components--pill--default, --variants, --sizes, --states, --with-icon-and-kbd, --composition.

Source: docs/status/pill.usage.md · GitLab