Icon button
canonSquare button carrying only an icon; label lives in the tooltip. Quiet at rest, variant="framed" for the bordered one, variant="secondary" (beta) for a hairline-bordered tool row.
variant="secondary" has ONE size (owner 19 Oct 2026, acrm ACRM-542): --control-h square + --ico-sm glyph; size/iconSize are ignored; text beside a row uses .acrm-icobtn-text (padding-inline --sp-4). Window headers stay quiet (AD 27 Sep 2026). variant="framed" (11 September 2026, BTN-A3) is a 1:1 port of the hand-drawn .pr-iconbtn / .rr-iconbtn, which were deleted with it: 32px wide on the base height, 1px --border-strong, --surface-100, --shadow-xs, --ico-sm glyph in --ink-700, hover --surface-75. Pressed, open and disabled deliberately keep the rest look, because the hand-drawn rules had no such states; the validator rule iconbtn-framed-raw blocks the old classes. Variant by CONTEXT (AD 26 Sep 2026, acrm !3910 — in the docstring): dense list row (ItemList, timeline, relations, thread, project/meeting/milestone row) = quiet size="sm"; detail header / toolbar = quiet default; variant="framed" ONLY next to framed siblings (rail action row, section header next to framed buttons, dialog footer next to Button default). Kebab = dots-vertical; a kebab with one item is replaced by the direct action (trash + confirm, link-off without danger) — rule "kebab with one action" landed as the wave C cleanup (AD 26 Sep 2026, acrm !3997: ProjectMilestonesSection trash, IdentityImageDialog trash, ProjectMeetingsTab link-off, none get a Popover around a single choice). Wave 16 (acrm !3997) finished the context rule above by migrating the last hand-drawn icon buttons onto it: 32px `.icobtn` (Uživatelé domains, segment detail/share ×2) → framed size default (30); 22px `.eml-icobtn` (draft/pinned mail rows) → `size="sm"`; 26px `.aichat-hbtn` (assistant panel header) → default, with `pressed` for the settings toggle; TabStrip scroll arrows → `size="sm"`. Exception kept: the App shell chrome's 32px `.as-icobtn` / `.as-util .icobtn` sits among 32–34px neighbours and stays hand-drawn. State pressed (acrm !3936): pressed?: boolean → aria-pressed + --active-surface, also on hover — for toggles (format, panel, template/AI in mail compose). The open look applies only to menu triggers ([aria-expanded=true][aria-haspopup]); a disclosure (collapse/expand) carries its state in the glyph. Link (`href`, AD 27 Sep 2026 — attachment download): renders `<a class="acrm-icobtn …">` with the same look, same pattern as `<Button href>` — real link semantics (middle-click, "save link as", a screen reader announces a link), `target="_blank"` gets `rel="noopener noreferrer"` merged in, `disabled` with `href` renders `<a aria-disabled>` without `href` (a link cannot be disabled by attribute). External/file URLs only — in-app navigation stays on the router. `ref` forwards onto the `<a>` too (review fix in !3997), so it can anchor a Popover. Touch target (owner 8 Oct 2026, acrm !4882, RULES §19): below 720 px an invisible ::after makes the tap area ≥ 44 × 44 without changing the look; checked on dev 8 Oct (⋮ top bar 42×44 at the screen edge, 0 stolen taps). Clipped targets fixed in acrm !4892 (checked on dev 8 Oct: Ajda 78×44, Pošta stars 44×44 except a row cut by the scroll edge). Remaining by design: column-header ⋮ 24×40 (header row is 40 px). Registry repaired 17 Oct 2026 (review F6): added exported Icon to react[].
Used in 474 files · 154 areas · 1727× — components/AiBadge, components/AppShell, components/Board, components/CallbackTakeoverModal, components/CheckMenu, components/DuplicatesPanel …
@acrm/ui <IconButton> <Icon>.acrm-icobtn .acrm-icobtn.sm .acrm-icobtn.framed .acrm-icobtn.secondarySourceStoryLegacy card buttons.htmlDefault
export const Default = () => <IconButton icon="pencil" title="Edit" aria-label="Edit" />;Variants
export const Variants = () => (
<div style={{ display: 'grid', gap: 'var(--sp-5)', justifyItems: 'start' }}>
<Labeled label="quiet (default)">
<IconButton icon="note" title="Nová poznámka" aria-label="Nová poznámka" />
</Labeled>
<Labeled label='framed — variant="framed"'>
<div className="pr-actions">
<IconButton variant="framed" icon="mail" title="Napsat e-mail" aria-label="Napsat e-mail" />
<IconButton variant="framed" icon="phone" title="Zavolat" aria-label="Zavolat" />
<IconButton variant="framed" icon="briefcase" title="Obchod" aria-label="Obchod" />
<IconButton variant="framed" icon="note" title="Nová poznámka" aria-label="Nová poznámka" />
<IconButton variant="framed" icon="checkbox" title="Nový úkol" aria-label="Nový úkol" />
</div>
</Labeled>
<Labeled label='secondary — variant="secondary" (candidate, ACRM-542): tool rows'>
<div style={row}>
<IconButton
variant="secondary"
icon="paperclip"
title="Přiložit soubor"
aria-label="Přiložit soubor"
/>
<IconButton variant="secondary" icon="template" title="Šablona" aria-label="Šablona" />
<IconButton variant="secondary" icon="signature" title="Podpis" aria-label="Podpis" />
<IconButton variant="secondary" icon="sparkles-2" title="AI" aria-label="AI" pressed />
</div>
</Labeled>
</div>
);Sizes
export const Sizes = () => (
<div style={row}>
<Labeled label="default · 30px (--control-h)">
<IconButton icon="dots-vertical" title="More" aria-label="More" />
</Labeled>
<Labeled label="sm · 24px">
<IconButton icon="dots-vertical" size="sm" title="More" aria-label="More" />
</Labeled>
<Labeled label="secondary · one size, 30px (size is ignored)">
<IconButton variant="secondary" icon="dots-vertical" title="More" aria-label="More" />
</Labeled>
</div>
);States
export const States = () => (
<div style={row}>
<style>{simulatedHover}</style>
<Labeled label="rest">
<IconButton icon="pencil" title="Edit" aria-label="Edit" />
</Labeled>
<Labeled label="hover (simulated)">
<IconButton icon="pencil" title="Edit" aria-label="Edit" className="is-hover" />
</Labeled>
<Labeled label="open (aria-expanded)">
<IconButton
icon="pencil"
title="Edit"
aria-label="Edit"
aria-haspopup="menu"
aria-expanded="true"
/>
</Labeled>
<Labeled label="pressed (toggle on)">
<IconButton icon="sparkles-2" title="AI" aria-label="AI" pressed />
</Labeled>
<Labeled label="disclosure (aria-expanded, no popup)">
<IconButton
icon="layout-navbar-collapse"
title="Collapse"
aria-label="Collapse"
aria-expanded="true"
/>
</Labeled>
<Labeled label="disabled">
<IconButton icon="pencil" title="Edit" aria-label="Edit" disabled />
</Labeled>
<Labeled label="secondary · rest">
<IconButton variant="secondary" icon="paperclip" title="Attach" aria-label="Attach" />
</Labeled>
<Labeled label="secondary · hover (simulated)">
<IconButton
variant="secondary"
icon="paperclip"
title="Attach"
aria-label="Attach"
className="is-hover"
/>
</Labeled>
<Labeled label="secondary · pressed">
<IconButton variant="secondary" icon="bold" title="Bold" aria-label="Bold" pressed />
</Labeled>
<Labeled label="secondary · disabled">
<IconButton
variant="secondary"
icon="paperclip"
title="Attach"
aria-label="Attach"
disabled
/>
</Labeled>
</div>
);Composition
export const Composition = () => (
<SectionTopBar
title={<SectionTopBarTitle name="Atol Capital s.r.o." avatar />}
style={{ width: '100%' }}
>
<SectionTopBarSpacer />
<IconButton
icon="lock-open"
title="Volné — nikdo zatím nevolá"
aria-label="Volné — nikdo zatím nevolá"
aria-haspopup="menu"
aria-expanded="false"
/>
<IconButton
icon="dots-vertical"
title="Další akce"
aria-label="Další akce"
aria-haspopup="menu"
aria-expanded="false"
/>
<IconButton icon="x" title="Zavřít" aria-label="Zavřít" />
</SectionTopBar>
);