Icon
Renders icons by name from any registered library — Phosphor (1,500+) and Lucide (1,900+) ship in `@arclux/arc-ui-icons` — with one-line library switching and custom icon registration.
<arc-icon> Overview
Icon renders SVG icons from a centralized icon registry by name. When the name property is set, the component dynamically imports the matching icon file, parses it through a DOMParser-based sanitizer that strips all <script> elements and on* event handler attributes, and injects the cleaned SVG into the shadow DOM. Parsed SVGs are cached in a module-level Map for efficient re-renders.
Icons are loaded per-icon on demand. Each of the 3,400+ icons is its own module (~500 bytes). When <arc-icon name="star"> renders, only star.js is fetched — not the entire library. This means importing arc-icon adds 0KB of icon data to your bundle upfront, and each icon you actually use costs only ~500 bytes. Bundlers automatically tree-shake unused icons out of production builds.
Icons live in their own package. @arclux/arc-ui-icons carries two libraries: Phosphor Icons (phosphoricons.com), a consistent set of over 1,500 glyphs with a clean filled style that works at any size, and Lucide (lucide.dev), a community fork of Feather Icons with over 1,900 stroke-based glyphs that pair well with lighter UI. Use the icon browser below to explore both and click any icon to copy its name.
Install it and import a pack. One line registers it, and if nothing else has been selected it becomes the active library — so <arc-icon name="star"> works on the next line:
import '@arclux/arc-ui-icons/phosphor';
Nothing is selected by default. Until a pack registers, every named icon renders its slot fallback and the registry logs one line saying so — it will not fail silently, but it will not guess either. Prior to v4 both packs were vendored inside @arclux/arc-ui, where they were 88% of the published files and 44% of the unpacked bytes of every install, icons or no icons.
To switch libraries globally, use the iconRegistry API or the declarative <arc-icon-library> component:
// JavaScript API
import { iconRegistry } from '@arclux/arc-ui';
iconRegistry.use('lucide'); // switch all icons to Lucide (no data loaded until icons render)
<!-- Declarative (place anywhere in the document) -->
<arc-icon-library name="lucide"></arc-icon-library>
You can also register custom icons on top of the active library. Custom icons are merged into the current set, so you can mix library icons with your own brand marks. Custom icons registered via set() resolve instantly with no network request:
iconRegistry.set({
'my-logo': '<svg viewBox="0 0 24 24">...</svg>',
'custom-chart': '<svg viewBox="0 0 24 24">...</svg>',
});
If your app uses a handful of icons, skip the pack registration and import those glyphs directly. This pulls in one module per icon and no resolver at all:
import { iconRegistry } from '@arclux/arc-ui';
import check from '@arclux/arc-ui-icons/phosphor/check';
import x from '@arclux/arc-ui-icons/phosphor/x';
iconRegistry.set({ check, x });
And if you need a whole library as a single import — an icon picker is the usual reason — that is still one line:
import phosphor from '@arclux/arc-ui-icons/all/phosphor'; // loads all ~1,500 icons
import lucide from '@arclux/arc-ui-icons/all/lucide'; // loads all ~1,900 icons
Five size presets — xs (12px), sm (16px), md (20px), lg (24px), and xl (32px) — control the rendered dimensions. The component inherits color from its parent via currentColor, so icon color naturally follows the surrounding text or container styling. When no matching name is found in the registry, the component falls back to rendering its default slot, allowing you to pass inline SVGs or custom content directly.
The label property controls accessibility behavior: when a label is provided, the icon wrapper receives role="img" and aria-label with the given text; when omitted, the icon is marked as role="presentation" with aria-hidden="true", hiding it from assistive technology. This two-mode approach ensures decorative icons stay silent while meaningful icons are properly announced.
Guidelines
When to use
- Provide a label for icons that convey meaning (e.g. status indicators, action icons)
- Omit the label for purely decorative icons so they are hidden from screen readers
- Use the size prop rather than CSS overrides to maintain consistent icon dimensions
- Register custom icons via the iconRegistry before first render
- Use currentColor inheritance by setting color on the parent element
When not to use
- Do not pass unsanitized SVG strings from user input — while the component strips scripts, defense in depth is wise
- Do not use the xl size for inline text icons; sm or md integrates better with body copy
- Do not set both a name and slot content simultaneously — the name lookup takes precedence
- Do not rely on the icon alone to communicate critical information; pair with visible text
- Do not hardcode fill or stroke colors in registered SVGs — use currentColor so they adapt to context
Features
- Per-icon lazy loading — only icons you use are fetched (~500 bytes each), 0KB upfront
- Two icon packs in `@arclux/arc-ui-icons`: Phosphor (1,500+ filled) and Lucide (1,900+ stroke-based)
- Registered, not bundled — core ships no icon data, and any library can register itself
- One-line library switching via iconRegistry.use() or `<arc-icon-library>`
- Custom icon registration — merge your own SVGs on top of any library (renders instantly)
- Five size presets: xs (12px), sm (16px), md (20px), lg (24px), xl (32px)
- Inherits color via currentColor for natural parent-driven styling
- Slot fallback when no registry name matches, allowing inline SVG passthrough
- Accessible role switching: `role="img"` with label, `role="presentation"` without
- XSS-safe: strips <script> tags and on* event handlers from SVG content
Preview
Usage
<!-- Basic usage, once a pack is registered (see the Registry API tab) -->
<arc-icon name="star" size="sm"></arc-icon>
<arc-icon name="heart" size="md"></arc-icon>
<arc-icon name="gear" size="lg"></arc-icon>
<!-- Accessible icon with label -->
<arc-icon name="warning" size="md" label="Warning"></arc-icon>
<!-- Switch to Lucide globally -->
<arc-icon-library name="lucide"></arc-icon-library> import { iconRegistry } from '@arclux/arc-ui';
import '@arclux/arc-ui-icons/lucide';
// Switch all icons to Lucide
iconRegistry.use('lucide');
// List all available icon names (async — loads manifest lazily)
const names = await iconRegistry.list(); // active library
const phosphor = await iconRegistry.list('phosphor'); // specific library
// Register custom icons (merged on top of active library)
iconRegistry.set({
'my-logo': '<svg viewBox="0 0 24 24">...</svg>',
});
// Look up an icon by name (async — loads single icon lazily)
const svg = await iconRegistry.get('star'); // returns SVG string or null import { Icon } from '@arclux/arc-ui-react';
export default function Example() {
return (
<>
<Icon name="star" size="sm" />
<Icon name="heart" size="md" />
<Icon name="gear" size="lg" label="Settings" />
</>
);
} <script setup>
import { Icon } from '@arclux/arc-ui-vue';
</script>
<template>
<Icon name="star" size="sm" />
<Icon name="heart" size="md" />
<Icon name="gear" size="lg" />
</template> <script>
import { Icon } from '@arclux/arc-ui-svelte';
</script>
<Icon name="star" size="sm" />
<Icon name="heart" size="md" />
<Icon name="gear" size="lg" /> import { Component } from '@angular/core';
import { Icon } from '@arclux/arc-ui-angular';
@Component({
imports: [Icon],
template: `
<arc-icon name="star" size="sm"></arc-icon>
<arc-icon name="heart" size="md"></arc-icon>
<arc-icon name="gear" size="lg"></arc-icon>
`,
})
export class MyComponent {} import { Icon } from '@arclux/arc-ui-solid';
export default function Example() {
return (
<>
<Icon name="star" size="sm" />
<Icon name="heart" size="md" />
<Icon name="gear" size="lg" />
</>
);
} import { Icon } from '@arclux/arc-ui-preact';
export default function Example() {
return (
<>
<Icon name="star" size="sm" />
<Icon name="heart" size="md" />
<Icon name="gear" size="lg" />
</>
);
} <!-- Auto-generated by @arclux/prism — do not edit manually -->
<!-- arc-icon — requires icon.css + base.css (or arc-ui.css) -->
<span class="arc-icon">
<span
class="icon"
role="presentation"
aria-label="Label"
aria-hidden="true"
>
</span>
</span> <!-- Auto-generated by @arclux/prism — do not edit manually -->
<!-- arc-icon — self-contained, no external CSS needed -->
<span class="arc-icon" style="display: inline-flex; align-items: center; justify-content: center; color: currentColor; vertical-align: middle">
<span
style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%"
role="presentation"
aria-label="Label"
aria-hidden="true"
>
</span>
</span> API
-
namestring'' - Icon name to look up in the icon registry. When provided, renders the matching SVG. When empty, falls back to slotted content.
-
size'xs' | 'sm' | 'md' | 'lg' | 'xl''sm' - Icon dimensions:
xs(12px),sm(16px),md(20px),lg(24px),xl(32px). -
labelstring'' - Accessibility label. When provided, sets
role="img"andaria-label. When empty, setsrole="presentation"andaria-hidden="true".
See Also
- Icon Button Compact button that renders an icon with optional text label, supporting ghost, secondary, and primary variants.
- Badge Compact pill-shaped label for status indicators, category tags, and notification counts. Seven color variants let you encode meaning at a glance across dashboards, tables, and card layouts.