Designer view
Icon
An SVG-based pictogram rendered inline with surrounding text or as a standalone glyph inside another component. Carries either decorative or meaningful semantics depending on whether a visible text label accompanies it. The canonical primitive that every larger component composes for affordance, status, or wayfinding cues.
Also called SVG Glyph Pictogram
When to use
Use
For affordance cues (chevron in a disclosure trigger, search glyph in a search input), status cues (check, exclamation, info), wayfinding cues (arrow, home, settings), and brand / decorative imagery that pairs with text. Always SVG when possible — token-colour flows through `currentColor`, sizing is driven by canonical size-tokens, and the asset scales cleanly across DPR.
Avoid
For substantive image content (photographs, illustrations) — that is `<img>` with alt text rather than an icon glyph. For icon-only buttons that lack an accessible name — use `Button` with `aria-label`, not a bare icon. For decorative imagery that exceeds the size-token range — use a brand-image or hero- media slot in the host component.
Versus related
- button
`Button` is an interactive activator that may host an Icon (icon-leading, icon-trailing, or icon-only). `Icon` is the glyph itself, never interactive on its own. Icon-only buttons compose Button + Icon and carry `aria-label` on the button; bare icons in a clickable host are an anti-pattern.
- link
`Link` is the navigation primitive; it may host an Icon (external-link arrow, download glyph) as a decorative cue alongside the link text. The Icon contributes visual semantics; the Link's text carries the accessible name.
- badge
`Badge` is a metadata marker that may host an Icon (icon-leading slot) for severity glyphs (check, exclamation). The Icon is decorative when paired with the badge content; meaningful when the badge is dot- only.
- avatar
`Avatar` is the entity-representation primitive that hosts Icon as its `fallback-icon` slot — the ultimate fallback when neither image nor initials are available. The Icon represents a generic-person glyph; Avatar represents the specific entity. The accessible-name for Avatar lives on the container, not on the Icon.
Icon is the canonical SVG-glyph primitive — a pictogram that renders inline with text or as a standalone glyph inside a host component. The semantic split is decorative versus meaningful: paired with a visible label, the icon is decorative and hides from assistive tech; without a label, the icon is meaningful and carries an accessible name. Two size families (token-driven and inline-with- text), two semantic modes (decorative, meaningful), three colour channels (stroke, fill, currentColor inheritance). The reference documents the role-img versus aria-hidden contract, the currentColor inheritance pattern that lets token-colour flow through, and the canonical sizing tokens that keep icons in scale with their host.
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
root | instance | Icon component instance from the icon-set library; size bound to a "size" component property |
stroke | frame | Vector path with stroke; stroke colour set to "currentColor" via icon-set token |
fill | frame | Vector path with fill; fill colour set to "currentColor" via icon-set token |
Token usage per slot
root- color
- foreground
color.text.primary
- foreground
- typography
- size
text.md
- size
stroke- color
- foreground
color.text.primary
- foreground
fill- color
- foreground
color.text.primary
- foreground
Figma ↔ Code property map
| Figma | Kind | Code | Notes |
|---|---|---|---|
Variant | Enum | variant | Maps outline / solid icon family. |
Size | Enum | size | Maps xs / sm / md / lg / xl. Bound to a token; never px. |
Name | Text | name | Selects an icon from the registry; both surfaces share the registry roster. |
Meaningful | Boolean | meaningful | When true, root carries role=img and the consumer must supply an aria-label. |
Has Stroke | Boolean | hasStroke | — |
Stroke | Slot | stroke | Vector outline path; renders only for outline-family icons. |
Fill | Slot | fill | Vector fill path; renders for solid-family icons. |
Motion
| Transition | Duration token |
|---|---|
rotate | motion.duration.slow |
pulse | motion.duration.base |
Responsive behaviour
| Breakpoint | Change |
|---|---|
breakpoint.sm | Below this width, default icon size collapses one step in the size scale (md → sm) for inline-with-text contexts. Standalone icons in icon-only buttons keep their explicit size to preserve hit-target. |
Internationalisation
RTL · mirroring
Direction-bearing icons (arrows, chevrons, back / forward glyphs) flip horizontally under `dir="rtl"`. Use the SVG `transform` attribute or a CSS `transform: scaleX(-1)` keyed off the `dir` attribute. Direction-neutral icons (search, settings, gear) render identically in both directions. Direction-bearing icons that depict a written-language convention (numerals, currency symbols) do NOT flip.
Text expansion
Icons themselves do not text-expand. The `aria-label` on meaningful icons may grow 30–50 % in German and Romance languages; SR reads the label without truncation, but visually the label is invisible (it lives in the accessibility tree only).
Variants, properties, states
Variants
Structurally different versions of the component.
outline solid Properties
The same component, parameterised.
| Property | Type |
|---|---|
size | xs | sm | md | lg | xl |
meaningful | boolean |
hasStroke | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | |
data | idleinheritingexplicit-colorreduced-motion |
Figma↔Code mismatches
- 01 Figma
A specific icon variant (e.g. "Search-outline-md") drawn as a separate component
CodeA single Icon component with a `name` prop selecting from an icon registry
ConsequenceDesigners ship N × M Figma components (every icon × every size); developers ship one Icon component with a name and a size prop. The Figma roster grows linearly with the icon set; the code roster stays at one. Drift is invisible because the Figma file does not enumerate "missing icons" against the code registry.
CorrectModel Icon as a single component with `name` and `size` props in both Figma and code. The Figma component carries a "name" component property bound to the icon-set token list; the code Icon imports from the same registry.
- 02 Figma
Icon size hard-coded as a px value
CodeSize driven by a token (`text.md`, or icon-specific tokens like `--ui-icon-md`)
ConsequenceDesigners set `width: 16px` directly on the icon frame; developers bind size to a token. When the type-scale rebalances, the Figma file lags and the shipped icon visually disagrees with the surrounding text size.
CorrectBind size to a token in both surfaces. Figma uses a "size" component property mapped to `xs / sm / md / lg / xl`; code uses the same enum. Icon size matches the host text size by default and falls back to explicit per-host overrides only when documented.
- 03 Figma
Icon colour drawn as a fixed hex
CodeColour driven by `currentColor` inheriting from the host text colour
ConsequenceDesigners fix a black or grey hue in the Figma frame; developers ship `fill="currentColor"` so the colour inherits from the host. The two diverge any time the host text colour is not the default; theming (light / dark) breaks the design-fidelity claim.
CorrectUse `currentColor` for fill and stroke in both Figma icon-set tokens and code. The icon colour follows the host text colour automatically; explicit colour overrides go through a documented `color` prop, not by hard-coding a hex.
- 04 Figma
Meaningful icon drawn without an accompanying label
CodeA bare `<svg>` with no `role="img"` and no `aria-label`
ConsequenceDesigners draw an icon that genuinely carries meaning (a "lock" indicating account locked, a "warning" indicating a risk) but provide no label. Developers ship the SVG as decorative; SR users encounter the locked / warning glyph without any AT signal.
CorrectDistinguish meaningful from decorative at design time. The Figma component property "meaningful" toggles the accessible-name field. Code reads the prop to either set `aria-hidden="true"` (decorative) or `role="img"` plus `aria-label` (meaningful). The two surfaces share the same flag.
Contracts
Non-negotiable contracts
APGWAI-ARIA aria-hidden + role=img patterns Decorative icons set `aria-hidden="true"` and no role. Meaningful icons set `role="img"` plus `aria-label` and no `aria-hidden`. Never both — the combination is a contradiction.
Without the binary distinction, browsers and SR resolve the combined attributes inconsistently. Some surfaces announce the label twice (once via role=img, once via the host); others ignore the icon entirely. The contract gives every consumer a deterministic announcement.
HTML specHTML inline SVG with currentColor keyword (W3C SVG + CSS Color) SVG `fill` and `stroke` are bound to `currentColor` so token-colour flows through CSS `color` inheritance. Hard-coded hex values in the SVG are reserved for brand-glyph cases documented at the host level.
Hard-coded hex breaks theming and forces icon-by-icon edits when the palette shifts. `currentColor` is the cheapest way to keep icons in step with their host text across light / dark / brand-swap themes without per-icon overrides.
Canon Icon-only buttons / links / badges (no visible label) carry the accessible name on the host element via `aria-label`, never on the icon itself. The icon stays decorative; the host carries the meaning.
Putting `aria-label` on the icon when it sits inside a button creates a name-from-content collision — SR-output may include both labels in unpredictable order. The host is the activator; the host owns the name.
Vocabulary drift
- HTML
svg / img- Native `<svg>` is the canonical icon-shape for inline glyphs; `<img>` with an SVG src is acceptable for non-inheriting icons but loses `currentColor` and token-driven sizing.
- Material 3
Icon (Material Symbols)- Material Symbols ships variable-axis icons (weight, grade, optical-size) on top of the canonical contract. Implementation audits document the per-axis mapping.
- Polaris
Icon- Carbon
Icon- Atlassian
Icon
Common mistakes
#icon-meaningful-no-label
Meaningful icon without an accessible name
The icon carries semantic meaning (lock, warning, error, external) but has no `role="img"` and no `aria-label`. Sighted users see the glyph; SR users encounter nothing where the meaning lives.
Set `role="img"` plus `aria-label` describing the icon's meaning. Never label the glyph itself ("checkmark", "exclamation-mark") — label the meaning ("Verified", "Warning"). When the icon is the entire content of a larger element (icon-only button, dot badge), move the accessible name to the host instead.
#icon-conflicting-aria-roles
Both `aria-hidden="true"` and `role="img"` set
The icon carries both `aria-hidden="true"` (says: ignore me) and `role="img"` plus `aria-label` (says: read this label). Browsers and SR resolve the contradiction inconsistently; the canonical semantic is undefined.
Pick one. Decorative icons set `aria-hidden="true"` and no role / label. Meaningful icons set `role="img"` plus `aria-label` and no `aria-hidden`. Lint the component to reject the combination.
#icon-color-not-currentcolor
Icon colour hard-coded
The icon's `fill` or `stroke` is set to a fixed hex or named colour. Theming (light / dark, brand swap) breaks because the icon stays its hard-coded hue while the surrounding text shifts. The icon visually disconnects from its host.
Use `fill="currentColor"` (filled icons) or `stroke="currentColor"` (outline icons) in the SVG. The colour inherits from the CSS `color` property of the host, following theme automatically. Explicit per-instance colour overrides go through a documented `color` prop or a host-level CSS variable, not through hard-coded hex values inside the SVG.
Accessibility hints
| Slot | Accessibility hint | |
|---|---|---|
root | Decorative icons set `aria-hidden="true"` (and no role). Meaningful icons carry `role="img"` plus `aria-label` describing the meaning ("Sort ascending", "Open menu"). Never both `aria-hidden="true"` and `role="img"` — they contradict. | |
stroke | Vector paths inside an SVG are presentational by default. No role needed; the SVG root carries the accessible name via `aria-label` when meaningful. | |
fill | Vector paths inside an SVG are presentational by default. No role needed. |