Skip to main content

icon — icons anywhere

UiIcon places an icon wherever a node is accepted: a UiStack child, a tree or list labelNode, a table cellTemplate. Most components already take an icon string for the common leading-icon case — this node covers free placement, and being a node it has an id, so a patch can REPLACE or REMOVE it on its own.

An icon carries only a token. What that token renders to is decided by a swappable icon layer — by default an SVG <use> into the curated sprite at /sui/icons.svg, sourced from Lucide (ISC-licensed).

Live — real tokens from icons/icons.json: semantic aliases, then raw sprite ids, then the status colour helpers and a legacy emoji. The bottom row shows the icon shorthand on an action and on a field.

Fields

FieldTypeMeaning
idStringNode id — set it if a patch needs to swap the icon later.
nameStringThe token: a semantic alias ("delete") or a raw sprite id with no alias ("chevron-right"). A non-token string (an emoji) is emitted verbatim.
titleStringAccessible label. With it the icon is role="img" + <title>; without it the icon is aria-hidden (decorative).
cssClassStringExtra CSS class — the status helpers sui-icon--success, --warning, --danger / --error, --muted live here.

There is no size field: .sui-icon is 1em × 1em and the stroke is currentColor, so an icon inherits the surrounding text's size and colour.

Building one

// The standalone node — free placement, patchable by id:
UiIcon.of("success"); // decorative
UiIcon.of("status", "success").labelled("All good") // id + a11y label
.withCssClass("sui-icon--success");

// The `icon` shorthand, for the common leading-icon case:
UiAction.primary("save", "Save").icon("save");
UiAction.secondary("edit", "Edit").icon("pencil") // icon-only button;
.appearance(UiAction.Appearance.ICON); // label → accessible name
UiField.text("q", "Search", null).asEditable().icon("search");
UiLink.of("back", "/x", "Back").icon("back");
UiSectionEntry.of("t", "Settings", body).icon("settings");

Tokens

A token is either a semantic alias or a raw sprite id. Aliases are declared in core/mc-semantic-ui-core/icons/icons.json as token → lucide-id, and that indirection is what makes the library swappable — app JSON that uses an alias survives a library change unchanged. The full alias set:

GroupTokens
Editingadd · edit · delete · remove · save · copy · close · check · confirm · cancel · send · reset · refresh
Navigationback · forward · up · down · home · menu · more · more-vertical · external · link · expand · collapse · first · last
Statussuccess · warning · error · info · help · loading · pending
People & settingsuser · users · settings · search · filter · sort · calendar · clock
Filesfile · document · folder · folder-open · image · download · upload · print
Markersmail · bell · star · favorite · bookmark · tag · flag
Securitylock · unlock · key · show · hide · logout · login · power
Data & techtable · list · grid · chart · dashboard · database · server · code · terminal · branch · cloud · globe
Miscmoney · euro · percent · location · phone · message · chat · share · flash · light · dark

Raw Lucide ids work too when they are in the sprite. Beyond the ids the aliases resolve to (trash-2, pencil, circle-check, …), the sprite carries a passthrough block of extra raw ids with no alias: chevron-up, chevron-down, chevron-left, chevron-right, circle-plus, circle-minus, circle-alert, arrow-up, arrow-down, arrow-left, arrow-right, smartphone, tablet, monitor, laptop.

Notes

Prefer the alias. delete and trash-2 render the same glyph today, but only the first one still means "delete" after the icon library is swapped. Use raw ids for the decorative cases where no alias fits.

The library is swappable, and no UiNode changes. Everything routes through one resolver: IconRenderer.setSpriteUrl(url) / setIconSpriteUrl(url) points at a different sprite, and IconRenderer.setResolver(...) / setIconResolver(...) replaces the scheme entirely — inline SVG, an icon font, anything. One call re-skins every icon on the page.

import { setIconSpriteUrl, setIconResolver } from "/sui/renderer.js";
setIconSpriteUrl("/assets/my-icons.svg");
setIconResolver((name, opts) => `<i class="fa fa-${name}"></i>`);

Unknown tokens fail visibly. A token that matches the sprite-id shape (^[a-z][a-z0-9-]*$) becomes an <svg><use href="…#token">; if the sprite has no such symbol the SVG renders empty — a blank slot, not a fallback glyph. Check the token against icons.json rather than guessing.

Any other string is a literal glyph. An emoji or arbitrary text does not match the token shape and is emitted verbatim in a <span>. That is the migration path: legacy icon: "📁" data keeps working while new data uses icon: "folder" — no flag day.

Changing the default set is a build step. icons.svg is a generated, committed artifact. Edit the token → lucide-id map in core/mc-semantic-ui-core/icons/icons.json, run npm run build:icons in mc-semantic-ui-core, and commit the regenerated sprite.

See also