Components · Actions
Icon button
A square, label-free action for toolbars and dense surfaces — a 32px ghost button holding a single 16px icon. Because it shows no text, an accessible name is mandatory.
Preview
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Icon button" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `icon-button` spec (variants: default, hover, disabled) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `icon-button` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
┌──────────────────┐
│ [icon] │ ← 32×32px container
└──────────────────┘
Parts:
- Container — 32×32px, transparent at rest, carries hover/active fill and focus ring
- Icon — 16px Material Symbol, inherits
{colors.ink}
| State | Token set | Visual change |
|---|---|---|
| Default | button-icon |
Transparent background, {colors.ink} icon |
| Hover | button-icon-hover |
{colors.primary-bg} fill, {colors.primary} icon |
| Active | button-icon-active |
{colors.surface-pressed} fill |
| Focus | button-icon-focus |
2px solid {colors.border-focus} outline |
| Selected | button-icon-selected |
{colors.primary-bg} fill, {colors.primary} icon, 1px solid {colors.primary} border |
| Disabled | button-icon-disabled |
{colors.disabled-text} icon, not-allowed cursor, 50% opacity |
Primary implementation reference: Table Action column. See Table component for usage context.
When to use
- Toolbar and action bar controls where label text would not fit.
- Secondary actions in dense list rows (edit, delete, more).
When not to use
- When the icon's meaning is not immediately obvious — add a label or use a standard button.
- As the primary action on a page — use
button-primaryinstead.
Do
- Always provide
aria-labelwith an action verb ("Edit", "Delete", "More options"). - Pair with a
tooltipto surface the label on hover. - Use
{icons.sizeMap.md}(16px) — do not override icon size inside this button.
Don't
- Don't use for navigation — use a link.
- Don't override
width/heightto make it smaller than 32×32px.
- Always provide
aria-labelwith an action verb: "Edit", "Delete", "More options", "Close". - Tooltip label must match
aria-labelexactly — do not paraphrase. - Use universally recognizable icons only:
edit,delete,close,more_vert,add,search,visibility. - Loading label: "Loading…" (visually hidden via
aria-live).
Keyboard
| Key | Behavior |
|---|---|
Tab |
Move focus to the button |
Enter / Space |
Activate the button action |
Accessibility
- ARIA role: implicit
role="button"on<button>. Addrole="button"andtabindex="0"if using a non-button element. - Required:
aria-label— the icon alone is not accessible. - Loading state: add
aria-busy="true"and a visually hidden label ("Loading…") during loading. - Contrast: icon color
{colors.ink}on transparent surface meets 3:1 UI element threshold.
Spec
Values and token references resolved straight from the Zetta spec.
button-icon
- backgroundColor
- transparent
- iconColor
- {colors.ink}
- borderColor
- transparent
- borderWidth
- 1px
- borderRadius
- {rounded.base}
- width
- 32px
- height
- 32px
- iconSize
- {icons.sizeMap.md}
- cursor
- pointer
- display
- flex
- alignItems
- center
- justifyContent
- center
button-icon-hover
- backgroundColor
- {colors.primary-bg}
- textColor
- {colors.primary}
button-icon-active
- backgroundColor
- {colors.surface-pressed}
button-icon-focus
- outline
- 2px solid {colors.border-focus}
- outlineOffset
- 2px
button-icon-selected
- backgroundColor
- {colors.primary-bg}
- iconColor
- {colors.primary}
- borderColor
- {colors.primary}
- borderWidth
- 1px
button-icon-disabled
- iconColor
- {colors.disabled-text}
- cursor
- not-allowed
- opacity
- 0.5
Known gaps
- Shape variant (circle) — not yet specified. Currently always
{rounded.base}. - Compact size (24×24px) — not yet specified as a token variant.