Skip to content

Icons

@absoluteui/icons gives every component its icons. It ships a small built-in set, has no third-party icon dependency, and builds icons with DOM APIs only (no HTML parsing), so icons work under a strict Content Security Policy with Trusted Types.

For an extensive library, bring the one you prefer: Font Awesome, Material Symbols and Material Icons, Lucide, or any set with SVG paths or shape nodes. You can also add icons, replace some or all of them with your design system’s icons, size and layer them. The Icons API page shows every built-in icon.

Components ask for icons by name ('close', 'calendar', 'chevron-down') or accept an inline definition in their icons option. A name is resolved in this order:

inline definition (used as given)
-> icon provider (setIconProvider; asked first for every name)
-> registry (registerIcons, then the built-in set)
-> error ICON_UNKNOWN

Every definition is validated before use: SVG path data, shape attributes, and viewBox must be well formed, class names must be plain CSS class names, ligature names plain words, and a factory must be a function. An invalid definition throws ICON_INVALID with the icon’s name.

Kind Use it for Shape
svg Icons you have as SVG path data or shapes (most icon sets) { kind: 'svg', viewBox: '0 0 24 24', paths?: ['M...'], nodes?: [['circle', { cx: 12, cy: 12, r: 9 }]], stroke?: true, strokeWidth?: 2 }
ligature Ligature icon fonts (Material Symbols, Material Icons) { kind: 'ligature', className: 'material-symbols-outlined', text: 'home' }
class Class-based icon fonts (Font Awesome’s web font, others) { kind: 'class', className: 'fa-solid fa-user' }
layers Several icons and counters stacked into one { kind: 'layers', layers: [{ icon: 'calendar' }, { text: '3', position: 'top-end' }] }
factory SVG sprites, libraries that give you elements, anything custom { kind: 'factory', create: (doc) => Element } (a new element per call)

stroke: true draws outlines in currentColor (like the built-in set); otherwise shapes are filled with currentColor. Shape nodes may be circle, ellipse, line, path, polygon, polyline, or rect with their geometry, fill or stroke of none or currentColor, fill rules, and opacity; anything else (event handlers, styles, links, scripts) is rejected. SVG icons take the text color; font icons look however the font styles them.

Icons are 1em by default, and components size their icons with the theme’s icon sizes:

Token Default Used for
icon-size-xs 0.75rem Dense indicators
icon-size-sm 1rem Most controls: buttons, inputs, menus, tabs
icon-size-md 1.25rem Larger controls, alerts, toasts
icon-size-lg 1.5rem Empty states, file lists
icon-size-xl 2rem Hero and illustration spots

Three ways to change sizes, from broad to narrow:

import { renderIcon } from '@absoluteui/icons';
// 1. Everywhere: change a theme token (or set it in your own theme).
document.documentElement.style.setProperty('--aui-icon-size-sm', '1.125rem');
// 2. One component or region: set --aui-icon-size on it or any ancestor.
const toolbar = document.createElement('div');
toolbar.style.setProperty('--aui-icon-size', '1.5rem');
// 3. One icon of your own: the size option (a token name or a CSS length).
toolbar.append(renderIcon('search', { size: 'lg' }), renderIcon('filter', { size: '20px' }));

--aui-icon-size wins over the component’s own size, and an icon’s size option wins over a surrounding --aui-icon-size. The spinner and checkbox or radio boxes are sized by their own options and tokens, not by --aui-icon-size.

registerIcons adds names, or replaces existing ones, everywhere at once:

import { registerIcons } from '@absoluteui/icons';
registerIcons({
// A new icon, usable as `icon: 'payroll'` in any component.
payroll: { kind: 'svg', viewBox: '0 0 24 24', stroke: true, paths: ['M4 6h16v12H4z', 'M8 12h8'] },
// Replaces the built-in close icon in every component that uses it.
close: { kind: 'svg', viewBox: '0 0 24 24', stroke: true, paths: ['M7 7l10 10M17 7L7 17'] },
});

Register icons once, at start-up, before components render.

A provider is an object with resolve(name). It is asked first for every icon name; returning undefined falls back to the registry, so a provider can replace only the names it knows:

import { setIconProvider, type IconDefinition } from '@absoluteui/icons';
// Your design system's icons, as path data.
const brandIcons: Readonly<Record<string, IconDefinition>> = {
close: { kind: 'svg', viewBox: '0 0 20 20', paths: ['M5 5l10 10M15 5L5 15'], stroke: true },
'chevron-down': { kind: 'svg', viewBox: '0 0 20 20', paths: ['M5 8l5 5 5-5'], stroke: true },
};
setIconProvider({ resolve: (name) => brandIcons[name] });

setIconProvider(null) removes the provider. Only one provider is active; to combine sources, resolve them inside one provider.

AbsoluteUI bundles no third-party icons: install the library you choose, import the icons you use, and register them. Only imported icons reach your bundle. The library’s licence applies to your application:

Library Package or source Licence Recommended form
Font Awesome Free @fortawesome/free-solid-svg-icons, -regular-, -brands- Icons CC BY 4.0, code MIT SVG with fromFontAwesome
Material Symbols Google Fonts (font), @material-symbols/svg-* (SVG files) Apache 2.0 SVG, or the font as ligature
Material Icons Google Fonts (font) Apache 2.0 Font as ligature
Lucide lucide ISC SVG with fromSvgNodes

Prefer SVG forms: they need no font download or third-party host, render crisply at every size, and are hidden from assistive technology exactly like the built-in icons.

Convert Font Awesome’s SVG icon definitions with fromFontAwesome. Register them under the names your code (or components) use:

import { faBell, faCalendarDays, faXmark } from '@fortawesome/free-solid-svg-icons';
import { fromFontAwesome, registerIcons } from '@absoluteui/icons';
registerIcons({
bell: fromFontAwesome(faBell),
calendar: fromFontAwesome(faCalendarDays), // every date picker now uses Font Awesome's calendar
close: fromFontAwesome(faXmark),
});

With Font Awesome’s web font instead, load its stylesheet (@fortawesome/fontawesome-free/css/all.min.css) and map names to classes: registerIcons({ bell: { kind: 'class', className: 'fa-solid fa-bell' } }). The Font Awesome JavaScript kit (a <script> from kit.fontawesome.com) is not recommended with AbsoluteUI: it rewrites the page’s DOM and injects its own styles, which conflicts with a strict CSP unless you relax it for the kit, and it duplicates what fromFontAwesome does at build time.

As a font from Google Fonts. Load the stylesheet, allow Google’s hosts in the CSP, and register icons as ligatures: the element gets the font’s class and the icon’s name as text (and translate="no", so page translation leaves it alone).

import { webFontHosts } from '@absoluteui/theme';
import { registerIcons } from '@absoluteui/icons';
const stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200';
document.head.append(stylesheet);
// Content-Security-Policy: style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com
console.info(webFontHosts.google.styleSrc, webFontHosts.google.fontSrc);
registerIcons({
home: { kind: 'ligature', className: 'material-symbols-outlined', text: 'home' },
calendar: { kind: 'ligature', className: 'material-symbols-outlined', text: 'calendar_month' },
});

Material Symbols is a variable font: its fill, weight, grade, and optical size are CSS, for example .material-symbols-outlined { font-variation-settings: 'FILL' 1, 'wght' 500; }. Material Icons (the older font) works the same way with family=Material+Icons and className: 'material-icons'.

As SVG files. Each Material Symbol is one path in a 0 -960 960 960 view box. Import the files your bundler can load as text (here Vite’s ?raw), or copy the path at build time:

import homeSvg from '@material-symbols/svg-400/outlined/home.svg?raw';
import { registerIcons } from '@absoluteui/icons';
const path = /\sd="([^"]+)"/.exec(homeSvg)?.[1] ?? '';
registerIcons({ home: { kind: 'svg', viewBox: '0 -960 960 960', paths: [path] } });

Lucide publishes each icon as shape nodes; fromSvgNodes converts them. Its style (24 x 24, 2 px round strokes) matches the built-in set, so it is a natural way to extend it. To make every component use Lucide, map the names components ask for in a provider:

import { Calendar, ChevronDown, ChevronLeft, ChevronRight, Search, X } from 'lucide';
import { fromSvgNodes, setIconProvider, type IconDefinition } from '@absoluteui/icons';
const lucideIcons: Readonly<Record<string, IconDefinition>> = {
close: fromSvgNodes(X),
calendar: fromSvgNodes(Calendar),
search: fromSvgNodes(Search),
'chevron-down': fromSvgNodes(ChevronDown),
'chevron-left': fromSvgNodes(ChevronLeft),
'chevron-right': fromSvgNodes(ChevronRight),
};
setIconProvider({ resolve: (name) => lucideIcons[name] }); // unmapped names keep the built-in icons

The built-in icon names are listed on the Icons API page.

Registering every icon of a library is convenient for prototypes but puts the whole library in your bundle (Lucide 1.49 has 1,857 icons; Font Awesome Free 7.3 solid has 1,422). For production, register the icons you use.

Other libraries, sprites, and custom elements

Section titled “Other libraries, sprites, and custom elements”

AbsoluteUI does not depend on any icon library; you connect one through a definition.

Libraries that ship path data. Many icon sets publish each icon as path data (or as SVG files you can convert at build time). Map them to svg definitions, in registerIcons or a provider:

import { setIconProvider } from '@absoluteui/icons';
// Shape of a typical package that exports path data per icon (placeholder data).
declare const iconData: Readonly<Record<string, { readonly viewBox: string; readonly paths: readonly string[] }>>;
setIconProvider({
resolve: (name) => {
const icon = iconData[name];
return icon ? { kind: 'svg', viewBox: icon.viewBox, paths: icon.paths, stroke: true } : undefined;
},
});

SVG sprites. A sprite is one SVG file with a <symbol id="..."> per icon. Use a factory that references the symbol; serve the sprite from your own origin so the page’s CSP allows it:

import { setIconProvider } from '@absoluteui/icons';
const SVG = 'http://www.w3.org/2000/svg';
const sprite = '/assets/icons.svg';
setIconProvider({
resolve: (name) => ({
kind: 'factory',
create: (doc) => {
const svg = doc.createElementNS(SVG, 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('width', '1em');
svg.setAttribute('height', '1em');
const use = doc.createElementNS(SVG, 'use');
use.setAttribute('href', `${sprite}#${name}`);
svg.append(use);
return svg;
},
}),
});

This provider answers every name; if your sprite does not have all the names components use, return undefined for the missing ones so the built-in icons fill in.

Libraries that render elements. When a library gives you an element (or an SVG string you already trust and parse yourself), wrap it in a factory. Factories must return a new element on every call, because the same icon can appear many times.

Icon fonts. If you have chosen an icon font, map names to its classes with class definitions and load the font’s stylesheet yourself. Class names are validated; they are the only thing AbsoluteUI adds:

import { registerIcons } from '@absoluteui/icons';
registerIcons({
star: { kind: 'class', className: 'icon-font icon-font-star' },
});

Icon fonts depend on the font loading and are harder to keep accessible; prefer SVG where you can.

A layers definition stacks up to eight icons and short texts (counters of up to four characters) into one icon, like a calendar with a count or a document with a status badge. Each layer is centred by default and can be:

Option Effect
scale 0.1 to 4
x, y Offset in em (-1 to 1), so it scales with the icon
rotate Degrees
flip 'horizontal', 'vertical', or 'both'
position 'center', 'top-start', 'top-end', 'bottom-start', 'bottom-end' (start and end follow the text direction)
tone 'primary', 'success', 'warning', 'error', or 'muted', from the theme
className Your own classes, for example a pill background behind a counter
import { registerIcons, renderIcon } from '@absoluteui/icons';
const dot = { kind: 'svg', viewBox: '0 0 24 24', nodes: [['circle', { cx: 12, cy: 12, r: 12 }]] } as const;
registerIcons({
'calendar-alert': { kind: 'layers', layers: [{ icon: 'calendar' }, { icon: dot, scale: 0.45, position: 'top-end', tone: 'error' }] },
});
const today = renderIcon({ kind: 'layers', layers: [{ icon: 'calendar' }, { text: '3', position: 'bottom-end', tone: 'error' }] }, { label: '3 events today', size: 'xl' });
document.body.append(today);

The layered icon is one element: it takes the size of the icon (every layer scales with it) and one accessible name, and its layers are hidden from assistive technology. A counter’s value belongs in the label, as above. Layers cannot contain further layers.

Components that show icons accept an icons option with their icon slots (listed in each component’s API reference). Pass a name or an inline definition; other instances keep their icons:

import { Spinner } from '@absoluteui/components/spinner';
const host = document.createElement('div');
document.body.append(host);
Spinner.create(host, {
label: 'Saving',
icons: { spinner: { kind: 'svg', viewBox: '0 0 24 24', stroke: true, paths: ['M12 3a9 9 0 1 1-9 9'] } },
});

Inline definitions are used as given; names still go through the provider and the registry.

renderIcon returns the same element components use. It is decorative by default (aria-hidden="true"); pass label when the icon itself carries meaning, which gives it role="img" and an accessible name:

import { renderIcon } from '@absoluteui/icons';
const status = document.createElement('p');
status.append(renderIcon('check-circle', { label: 'Approved' }), ' Leave request');

Icon-only buttons are labelled on the button, not the icon: use the component’s label option (for example Button.create({ icon: 'menu', label: 'Open navigation' })).

  • Register icons or set the provider once, before components render.
  • A provider may answer only some names; return undefined for the rest.
  • Factories return a new element per call and build it with DOM APIs.
  • Import and register only the icons you use; register a whole library only for prototypes.
  • Serve sprites and icon fonts from your own origin, or allow their host in your CSP (Google Fonts: webFontHosts.google).
  • Keep the icon library’s licence notice (Font Awesome Free requires attribution).
  • Size icons with the icon-size-* tokens, --aui-icon-size, or size, not by overriding component CSS.
  • Give meaningful standalone icons a label; label icon-only controls on the control.