Pill
canonOutlined label whose colour is carried by a saturated dot: seven hues for distinctness, plus one reserved tone, critical.
No React component of its own — TagPills, StatusChip and the listing columns all emit .acrm-pill. AD 6 Sep 2026 (after Linear): the tinted ground was replaced by an outlined chip with a saturated --ic-* dot and full-contrast text. The old ground measured only 1.03-1.19:1 against the canvas, so in dark theme the tone did almost nothing and the colour was really carried by the text; it also left grey with no shape at all and needed a background AND an ink per tone. A label with an icon has no dot (:has(.ti)). The .dot class is now a no-op. There is deliberately no exception for tables: dropping the frame there needed two conditions, and the second (label alone in the cell) would make the same column render differently for a record with one tag than for one with two. AD 5 Sep 2026: the two-family tone model was dropped; red is the one reserved word. Leading icon from the whole Tabler set (owner 27 Sep 2026, acrm !4004): `TablerIcon` lazy-fetches `/tabler/<name>.svg`, caches it, restrokes to 1.75 and wraps it in `.ti` — same no-dot rule, DS `<Icon>` allowlist unchanged, app-level escape hatch for AI-picked meeting-topic glyphs only. "Section tags" composition (acrm !4013): removable `.acrm-pill` row with no frame + a quiet section action opening a `Popover` with one `Input` to add — used by meeting Minutes "Probraná témata"; a framed tag input stays a form-only control. Pill wave acrm !4079 (owner-approved 28 Sep 2026, variant A): ~20 hand-made "pills" with tinted backgrounds (20–24 px, 10–12 px type) replaced by ONE shape — `.acrm-pill` sm, 20 px, 12/500, outline + tone dot, no tinted fill. Rule: a CATEGORY (role, owner/share, segment) = `.acrm-pill` with a tone dot; a STATE (active/inactive, free/busy, call state, meeting state, prediction, import match, user status) = status chip (`.acrm-pill sm` + `data-status`); no new pill variant. Busy room = amber, never red (red is reserved for errors). Room equipment is NOT a pill: quiet text with a 14 px DS glyph per item (map apps/web/src/lib/room-equipment.ts, unknown item = text only), no dots between items (the icon separates), --fg-muted. Kept aside: `.grade` (credit grade) and `.pdr-pill`. The same MR fixed meeting status always showing grey "done". Leading icon carries the tone (owner 8 Oct 2026, acrm !4915, AD FA258-re no. 5): a pill with a DIRECT leading icon child has no dot (`:has(> .ti)`, not `:has(.ti)` — the remove cross `.x .ti` is a grandchild and must not take the dot away) and the icon is coloured by the tone instead: `.acrm-pill > .ti { color: var(--pill-dot, var(--ic-grey)) }`. General rule for every pill with a leading icon, not a special case of one component; first consumer is Project label.
Used in 9 files · 9 areas · 11× — components/TagsEditor, components/TagsField, components/projects, pages/CompanyRecordPage, pages/DialerPage, pages/LidePage …
apps/web <TagPills> <StatusChip> <ContactStatusChip>.acrm-pill .acrm-pill.sm .acrm-pill::before .acrm-pill .x +2SourceStoryLegacy card pills-status.htmlLegacy card colors-pills.htmlThe 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 (StatusChipusesgreenfor stagewonand statusclientbecause 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 abutton.xfor 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 .tidoes 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 isproject-label. - Frame
1px solid var(--border), no fill, textvar(--fg). - Geometry: height 22px, padding
0 10px 0 8px(tighter on the dot side, because a dot is narrower than a glyph);.smis 20px /0 8px 0 7px. .dotis 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
- Pick palette hues for distinctness from their neighbours.
- Use
.sminside 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
.xanaria-labelnaming what it removes (Odebrat VIP). - Put the pills that label a list row into the
tagsslot of Item list, not into a column. - Keep
criticalfor things that are actually wrong, so red stays trustworthy.
- Bring
positive,warning,infoor a pillaccentback. 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.
Related
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 itstagsslot.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