Design HubStudio

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."

stroke · 2px
stroke · 2px
stroke · 2px
stroke · 2px
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.

Online Pending
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.

Icon + text vs. text-only
Icon + text — when the icon adds recognition
Use when the icon is genuinely helpful — a familiar glyph that lets the user pattern-match faster than reading the label (plus = create, magnifying glass = search, chevron = navigate). The text still does the work; the icon accelerates scanning.
Text-only — when no icon means it
Most product-specific actions don't have a universal icon. Compile, Stage, Approve, Allocate — there's no glyph for these. A generic checkmark or arrow is decoration, not signal. Skip it.
Icon-only vs. labelled
Icon-only — for universal, learned actions
Search, close, filter, overflow menu, settings gear, back/forward. Icons users have seen in dozens of products and instantly recognize. Always pair with aria-label — screen readers announce nothing meaningful otherwise.
Labelled — for product-specific actions
Domain verbs need their text. Approve run, Abort, Stage plate — no icon makes these instantly meaningful. Even with an icon, the text carries the load. Don't strip it to save space; you'll lose the user.
Decorative vs. meaningful (for screen readers)
Decorative → aria-hidden="true"
The breadcrumb chevrons, status-badge dots, decorative dividers — anything that's purely visual reinforcement of adjacent text — should be hidden from screen readers. Otherwise the SR announces "right angle bracket Workflows right angle bracket Library right angle bracket PCR amplification."
Failed
Meaningful → labelled, never hidden
The X next to "Failed" reinforces the status visually AND for SR users (the badge text already says "Failed"; the icon is redundant for SR — make it 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 without aria-label
    UX
    A <button> with only an SVG inside announces nothing to screen readers. Every .btn-icon-only needs aria-label naming 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 families
    Principle 1
    Lucide 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"
    UX
    Universal 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 values
    Principle 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 icons
    Principle 4
    Icons 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 readers
    UX
    The 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.

  • universal icons, no labels Back / Search / Filter / Sort / Export / More — all instantly recognizable; each carries aria-label
  • labelled icon for THE action "New run" pairs plus icon with text; the icon accelerates scanning, the text says what gets created
  • decorative icons hidden The search-box leading icon and toolbar divider are aria-hidden="true" — they reinforce the input visually but the placeholder already says "Search runs…"
  • one size, one family All icons sit on --icon-size-md, all stroke style, all 2px stroke. The toolbar reads as one piece, not a collection