<ui-icon>

An SVG icon loader that fetches and caches Phosphor icons on demand. Use <ui-icon name="…"> for icons from the built-in Phosphor set; use the src attribute for any other SVG URL. Prefer <ui-icon> over a plain <img> for icons because it paints with currentColor, scales with font-size, and supports offline reuse via the Cache API. Use a plain <img> instead when the image is photographic or carries intrinsic dimensions that must not follow the text flow.

ui-icon.mjs

ElementAttributesPropertiesMethodsEvents
<ui-icon> name, src, weight (observed — changing one on a mounted icon swaps the glyph), data-error (set by component on failure: "missing" | "parse"; cleared when a later load succeeds) name, src, weight (each reflects its attribute) UiIcon.preload(names, {weight}?), and the overridable statics UiIcon.icon_url(name, weight) / UiIcon.normalize_svg(svg) ui-icon-error (bubbles, detail: {name, url, reason})

A named icon resolves to /phosphor-icons/SVGs/<weight>/<name>.svg, with weight defaulting to regular. Only the regular weight is vendored, so any other weight value 404s until that weight's SVGs are added alongside it.

UiIcon.preload(names, {weight}?) fetches icons into the Cache API ahead of use, so a glyph first needed while offline is already stored. A subclass serves a different icon set by overriding two statics: icon_url(name, weight) builds the URL from UiIcon.base, and normalize_svg(svg) post-processes the parsed SVG — the default fills with currentColor so the host's color paints the glyph, and thickens Phosphor's hairline strokes.

1. Custom SVG (src)

Use the src attribute for SVGs outside of phosphor-icons.

Show code

2. Missing icon (404)

Sets data-error="missing" on the host and dispatches a bubbling ui-icon-error event. Default fallback is a red file-x — override the ui-icon[data-error="missing"]::before rule, which ui-icon.mjs injects ahead of the page's stylesheets, with a rule of your own. A transient network failure is not a missing icon: it sets no data-error, fires no event, and drops the in-memory entry so the next mount retries. An unparseable response sets data-error="parse" and fires the same event with reason: "parse".

Show code

3. Swapping the icon at runtime

name, src and weight are observed: setting one on a mounted icon reloads the glyph, so callers repoint an icon in place instead of replacing the element. A name already in memory swaps without a repaint gap. Swapping away from a 404 also clears data-error and its red fallback.

Show code

4. Phosphor Regular Icons

All icons (phosphoricons.com). Hover for name, click to copy markup.

Accessibility (WCAG)

Icons are decorative by default: the component sets aria-hidden="true" on the host automatically, so screen readers skip it (WCAG SC 1.1.1 — Non-text Content). For a meaningful icon, add aria-label or aria-labelledby directly on the element — the component will not overwrite an existing label.

<ui-icon> is neither focusable nor interactive. Put keyboard interaction on its surrounding <button> or <a> (WCAG SC 2.1.1 — Keyboard).

ARIA wiring: the shadow root is mode="open" and contains only a raw <svg>. The SVG itself carries no ARIA role; the host element's aria-hidden / aria-label propagate through the shadow boundary because they live on the host (WCAG SC 4.1.2 — Name, Role, Value).

Known limitations: a failed icon exposes no fallback text to assistive technology — a 404 paints the red file-x glyph and a network failure paints nothing at all, but neither is announced. Callers that need a guaranteed fallback should supply aria-label on the host so the label is announced even when the SVG is absent (naming the icon also suppresses the automatic aria-hidden).

Decorative (default)

No extra markup needed. aria-hidden is applied automatically.

<button> <ui-icon name="trash"></ui-icon> Delete </button>

Meaningful

Add aria-label when a standalone icon conveys information that surrounding text does not explain. For an icon inside a button, name the button as described below.

<button aria-label="Delete"> <ui-icon name="trash"></ui-icon> </button> <!-- label the icon itself when the surrounding element cannot carry the name --> <span> <ui-icon name="warning" aria-label="Warning"></ui-icon> Unsaved changes </span>

Icon-only buttons: put the accessible name on the <button>, not the icon. See Accessibility §Screen reader labels.

See also