14. Icons & glyphs
Icons aren't decoration — they're a small visual vocabulary that shrinks copy and reinforces meaning. Every icon decision is a taste decision: which size, paired with text or alone, decorative or labelled, sourced from where. Small surface, lots of drift potential.
Sizes
Five tokens: --icon-size-xs/sm/md/lg/xl. Use these, not pixel values. md (16px) is the default — it pairs cleanly with body text and the default button height.
- xs · 12pxInline with caption / mono text. Dense tables.
- sm · 14pxSmall buttons (
.btn-sm), secondary chrome. - md · 16px (default)Default buttons, inline with body text. The cheapest choice.
- lg · 20pxLarge buttons (
.btn-lg), section headers. - xl · 28pxEmpty-state hero, feature highlights. Rare.
Style — stroke, used consistently
The Studio ships line/stroke icons (the Lucide / Heroicons / Phosphor outline-set aesthetic): 2px stroke, rounded caps and joins, no fill. The choice matters less than the consistency — mixing stroke and filled glyphs in the same view reads as "two products glued together."
In context
Icons live inside other primitives. Notice each context uses a different size — .btn-icon auto-scales to match the button variant; status dots are 8px circles; the search input shows a leading sm icon.
When to use each
Three decisions that compound: should the action carry an icon at all, should it be icon-only, and what gets a screen-reader label.
aria-label — screen readers announce nothing meaningful otherwise. aria-hidden="true"aria-hidden). The trash icon-only button is the action — it gets aria-label="Delete". Rule of thumb: if removing the icon would lose information for sighted users, keep it visible AND label it. If removing it changes nothing, hide it from SR. Drift to avoid
Six named anti-patterns. Icon drift is sneaky because each violation feels harmless in isolation — and then you have a product with three icon libraries and four sizes you can't explain.
- Don't ship icon-only buttons withoutUX
aria-labelA<button>with only an SVG inside announces nothing to screen readers. Every.btn-icon-onlyneedsaria-labelnaming the action ("More actions", "Search", "Close"). This is non-negotiable; it's not "accessibility theatre" — without it, the button is functionally invisible to a chunk of your users. - Don't mix icon familiesPrinciple 1Lucide for buttons, Heroicons for nav, Material for the avatar menu — three families means three stroke widths, three corner radiuses, three takes on what a "settings gear" looks like. Pick one library per product. Mixing reads as "glued-together" even when adopters can't articulate why.
- Don't replace text labels with icons to "save space"UXUniversal icons (close X, search, gear, plus) work standalone. Product-specific actions (Compile, Stage, Approve) do not. There's no shared glyph for "compile a method" — any icon you pick is a guess the user has to learn. When in doubt, keep the label. Space pressure means you have too many buttons, not labels that are too long.
- Don't size icons with inline pixel valuesPrinciple 2
style="width: 18px"picks an arbitrary size between md and lg. Use the tokens (--icon-size-xs/sm/md/lg/xl) so density changes scale icons consistently with everything else. In-between values fragment the scale and erode the system. - Don't decorate with iconsPrinciple 4Icons next to every section heading, sparkles around the page title, a flag emoji on each row — pure decoration that adds visual noise without adding information. Same rule as color: if it doesn't carry meaning, it shouldn't be there. Icons cost attention; spend that budget on icons that say something.
- Don't leave decorative icons readable to screen readersUXThe chevron in Workflows › Library › PCR is visual scaffolding — without
aria-hidden="true"the SR announces every separator out loud, turning a breadcrumb into "Workflows greater-than Library greater-than PCR." Same for the dot inside a status badge: the badge text already names the state.
Composition — table toolbar
A toolbar where every icon decision is intentional: labelled icon-buttons for universal actions on the left, primary labelled action on the right, decorative icons hidden from screen readers, every icon-only button carries an aria-label.