registry:ui · General
icon — a name, not a picture
The glyph renderer every component shares. You hand it a NAME from the generated set — the IconName union closes at build time, so a misspelled glyph is a compile error, not a blank square in production. The component owns the whole <svg> root: viewBox from the icon data, the square size edge, currentColor painting by artwork nature (stroke artwork strokes, fill artwork fills), round caps and joins, aria-hidden. Sizing and stroke weight are props — never wrapper CSS fighting an inline svg.
$npx jixoai-ui add icon
https://ui.jixoai.com/r/icon.json
usage
Usage
Import the component, hand it a name — the control around it carries the meaning.
<script lang="ts">
import Icon from '@ui/icon';
</script>
<!-- decorative by contract — the accessible name lives on the control -->
<button aria-label="close">
<Icon name="x" />
</button>
<!-- size + stroke weight are props; class + attrs land on the root -->
<Icon name="chevronRight" size={13} />
<Icon name="folderOpen" size={20} strokeWidth={1.5} class="text-primary" />icon
One name renders one glyph. The playground drives the lower instance; the upper rows are the default sizes, the stroke ladder, and currentColor theming.
size=12size=16size=24size=32sw=1.5sw=2sw=2.5<Icon name="rotateCcw" size=20 strokeWidth=2 /><script lang="ts">
import Icon from '@ui/icon';
</script>
<!-- decorative by contract — the accessible name lives on the control -->
<button aria-label="close">
<Icon name="x" />
</button>
<!-- size + stroke weight are props; class + attrs land on the root -->
<Icon name="chevronRight" size={13} />
<Icon name="folderOpen" size={20} strokeWidth={1.5} class="text-primary" />law
The name is a union, not a string
IconName is generated together with the set: the union and the runtime data come from one artifact, so the compiler and the renderer can never disagree. A typo fails svelte-check at the exact prop — it never becomes a runtime blank, a silent fallback, or a shipped misspelling.
<Icon name="chevrinRight" />
~~~~~~~~~~~~~~~~ Type '"chevrinRight"' is not assignable to
type 'IconName' — the union is generated with the artifact,
so a typo is a COMPILE error, never a shipped blank glyph.The union, the iterable (ICON_NAMES) and the loaders
(getIcon / loadIcon / preloadIcons) all ride the one generated module — $lib/icon-set.gen. Its config, budgets and install
tiers live on the icons page.
async semantics
Two paths, one box
The default case is synchronous: the artifact's inline chunk answers getIcon() immediately, so server-side rendering paints the glyph and hydration matches with zero wiring. Lazy chunks exist only past the budget (or behind inlineFirstChunk: false) — their pending and rejected markup are the IDENTICAL reserved span, so hydration never rewrites the box and the layout never shifts.
import { loadIcon, preloadIcons } from '$lib/icon-set.gen';
// inline-core names answer synchronously — SSR paints them in the
// server HTML, hydration matches, zero wiring owed. Names past the
// chunk budget ride loadIcon(): one cached dynamic import per chunk.
await loadIcon('fileVideo'); // pull one lazy chunk
await preloadIcons(['folderOpen', 'fileAudio']); // warm a set ahead of a mount
// while pending — and after a rejected chunk — the component renders
// the FIXED reserved box (pending and rejected are the IDENTICAL span,
// so hydration never rewrites it and the layout never shifts):
// <span data-jx-icon-pending aria-hidden="true"
// style="display:inline-block;width:20px;height:20px"></span>
// a failed chunk warns once per chunk per session; the box stays.a11y
Decorative by contract
Every glyph renders aria-hidden — an icon is never the accessible name. The meaning lives in the surrounding text or the control's aria-label.
ARIA
| Attribute | Value | Description |
|---|---|---|
| aria-hidden | "true" (baked in) | The glyph is decoration; it never joins the accessibility tree and cannot become a name. |
| aria-label | consumer-owned | Icon-only controls name themselves (e.g. on the wrapping button) — the component offers no label prop on purpose. |
| data-jx-icon | on the root | Stable hook for consumer CSS that must target the rendered svg. |
axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. §13: size numbers are the axis' number lane VERBATIM (px, same semantics — explicit numbers additionally stamp the size carrier); strings and the absent state (the density ruler's --jx-icon) stay the glyph's own per its recorded no-ambient-size law.
Icon · universal props
<!-- §13: the number lane verbatim -->
<Icon name="check" size={24} />
<Icon name="check" size="1.25em" />
<Icon name="check" />api
API
Three props and a verbatim spread — the component owns every other root attribute.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| name * | IconName | — | The glyph's name in the generated set ($lib/icon-set.gen). The union closes at build time — a typo is a compile error. |
| strokeWidth | number | string | 2 | Stroke weight — stroke artwork only; inert on fill-nature glyphs. |
| class | string | '' | Lands on the root <svg> verbatim (currentColor theming rides the text color utilities). |
| ...rest | SVGAttributes<SVGSVGElement> | spread | Every other attribute lands on the root; the component also owns xmlns, viewBox, fill/stroke by artwork nature, round caps/joins, aria-hidden and data-jx-icon. |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |