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.htmlDefault
export const Default = () => <span className="acrm-pill blue">Klient</span>;Variants
export const Variants = () => (
<div style={{ display: 'grid', gap: 'var(--sp-4)' }}>
<Row text="the seven hues — pick by distinctness from the neighbours; the dot carries the colour, the text stays at full contrast">
{['lime', 'amber', 'blue', 'purple', 'green', 'rose', 'grey'].map((t) => (
<span key={t} className={'acrm-pill ' + t}>
{t}
</span>
))}
</Row>
<Row text="the one reserved tone — red means something is wrong, and nothing else may claim it">
<span className="acrm-pill critical">Prohráno</span>
</Row>
<Row text="the `dot` class is now a no-op — every label has a dot; it is kept only so old call sites do not break">
<>
<span className="acrm-pill green dot">Aktivní</span>
<span className="acrm-pill amber dot">Čeká</span>
<span className="acrm-pill grey dot">Neaktivní</span>
</>
</Row>
</div>
);Sizes
export const Sizes = () => (
<div style={{ display: 'grid', gap: 'var(--sp-4)' }}>
<Row text="default — page content, cards, rail">
<>
<span className="acrm-pill blue">Klient</span>
<span className="acrm-pill purple">Partner</span>
</>
</Row>
<Row text="sm — inside a table row or another dense line, where the default would push the row height">
<>
<span className="acrm-pill blue sm">Klient</span>
<span className="acrm-pill purple sm">Partner</span>
</>
</Row>
</div>
);States
export const States = () => {
const [tags, setTags] = useState(['Klient', 'VIP', 'Praha']);
return (
<div style={{ display: 'grid', gap: 'var(--sp-4)' }}>
<Row text="removable — click the × on a tag; the row reflows, nothing else moves">
<>
{tags.map((t, i) => (
<span key={t} className={'acrm-pill ' + ['blue', 'purple', 'lime'][i % 3]}>
{t}
<button
className="x"
aria-label={'Odebrat ' + t}
onClick={() => setTags((xs) => xs.filter((x) => x !== t))}
>
<Icon name="x" />
</button>
</span>
))}
{tags.length === 0 ? (
<span style={{ font: 'var(--p)', color: 'var(--fg-muted)' }}>Bez štítků</span>
) : null}
</>
</Row>
<Row text="reset">
<button className="acrm-btn" onClick={() => setTags(['Klient', 'VIP', 'Praha'])}>
Vrátit štítky
</button>
</Row>
<Row text="static — a pill that is not removable carries no button and no hover affordance">
<span className="acrm-pill green">Vyhráno</span>
</Row>
</div>
);
};With icon & kbd
export const WithIconAndKbd = () => (
<div style={{ display: 'grid', gap: 'var(--sp-4)' }}>
<Row text="with a leading icon — only where the icon says something the word does not (channel, source, owner type)">
<>
<span className="acrm-pill blue">
<Icon name="mail" />
E-mail
</span>
<span className="acrm-pill grey">
<Icon name="phone" />
Telefon
</span>
</>
</Row>
<Row text="icon + remove — the tag input shape: meaning on the left, the way out on the right">
<span className="acrm-pill purple">
<Icon name="tag" />
Kampaň Q3
<button className="x" aria-label="Odebrat Kampaň Q3">
<Icon name="x" />
</button>
</span>
</Row>
<Row text="no shortcuts — a pill is a label, not a control; the keyboard reaches the × because it is a real button">
<span className="acrm-pill lime">Štítek</span>
</Row>
</div>
);Composition
export const Composition = () => (
<div
style={{
width: 460,
border: '1px solid var(--border)',
borderRadius: 'var(--radius-lg)',
background: 'var(--bg-canvas)',
overflow: 'hidden',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 'var(--sp-3)',
padding: 'var(--sp-4)',
borderBottom: '1px solid var(--border)',
}}
>
<span style={{ font: 'var(--h2)' }}>ATOL Capital</span>
<span className="acrm-pill green sm dot">Aktivní</span>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--sp-2)', padding: 'var(--sp-4)' }}>
<span className="acrm-pill blue sm">Klient</span>
<span className="acrm-pill purple sm">Partner</span>
<span className="acrm-pill lime sm">Praha</span>
<span className="acrm-pill grey sm">Import 2026</span>
</div>
</div>
);