Icon
A functional glyph, or a mark generated from a name, drawn in the current text color.
Live demo
Icon renders one glyph from a small functional set (chevrons, arrows, close, check, the status marks, a spinner, the menu dots, and the media-transport family: play, pause, stop, skip-forward, skip-back) as inline SVG. It carries no color of its own: the glyph is drawn in currentColor, so it inherits the text color around it and matches the derived theme with nothing to wire.
It sizes off the surrounding type by default, so an icon set beside a word lines up with it; size steps it in fixed em for a standalone glyph. An optional tone tints it to a semantic role or named hue, spin turns it into a loading affordance, and a label promotes it from decorative to a named image for assistive tech. Beyond the lookup, a name can carry a spec: a terse grammar (shield--star-s45-c1) the engine parses into a layered mark and composes on the fly, placing primitives on a grid, sizing, rotating, outlining, or knocking them out, and coloring them from one of the theme's own palettes via colors so a generated mark recolors with the theme (a ---ps-{palette} finish pins the palette in the name instead, so a mark carries its own). A parametric letter primitive typesets a glyph as a mark (--letter-A) in an indexed theme font slot (f0 sans, f1 display, f2 mono) that a ---f finish can swap for another theme font or a named web font. Lookup for the common glyph, generation for everything else, one element for both.
When to use
How this component composes with the rest of the set.
Props
7 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
Sizes
sm
A compact glyph for dense inline hints.
md
The default glyph, matching body text.
lg
A prominent glyph for a featured control.
xl
A large glyph for an empty-state or hero mark.
States
spin
Rotates continuously as a loading affordance; suppressed under reduced-motion.
Anatomy
The named parts that make up the component, with their selectors.
icon
The <svg> glyph, drawn in currentColor on a 24×24 grid.
Tokens & coverage
What the component consumes, checked live against what the algorithm produces.
Live coverage check against the xtyle-default register
(derive(xtyleDefault, { anchors }) →
coverComponent(manifest, register)). Every token this component
consumes must be a key the algorithm produces.
--accent
--accent-2
--accent-3
--accent-4
--black
--blue
--brown
--cyan
--danger
--gray
--green
--info
--neutral
--orange
--pink
--purple
--red
--success
--warn
--white
--yellow
Accessibility
Code
Glyphs and sizes
A few glyphs from the set, and the size steps.
<xtyle-icon name="search"></xtyle-icon>
<xtyle-icon name="check"></xtyle-icon>
<xtyle-icon name="chevron-right"></xtyle-icon>
<xtyle-icon name="menu" size="sm"></xtyle-icon>
<xtyle-icon name="menu" size="lg"></xtyle-icon>
<script lang="ts">
import { Icon } from "@xtyle/svelte";
</script>
<Icon name="search" />
<Icon name="check" />
---
import Icon from "@xtyle/astro/Icon.astro";
---
<Icon name="search" />
<Icon name="check" />
Toned and spinning
A tone colors the glyph; spin turns the loader into a busy indicator.
<xtyle-icon name="success" tone="success"></xtyle-icon>
<xtyle-icon name="warning" tone="warn"></xtyle-icon>
<xtyle-icon name="error" tone="danger"></xtyle-icon>
<xtyle-icon name="loader" spin label="Loading"></xtyle-icon>
<script lang="ts">
import { Icon } from "@xtyle/svelte";
</script>
<Icon name="success" tone="success" />
<Icon name="loader" spin label="Loading" />
---
import Icon from "@xtyle/astro/Icon.astro";
---
<Icon name="success" tone="success" />
<Icon name="error" tone="danger" />
Generated marks
A name carrying a spec is composed on the fly: primitives on a grid, sized / rotated / outlined / knocked out, colored from the theme's series via colors.
<!-- a name carrying a spec is composed, not looked up -->
<xtyle-icon name="crest--shield-c1--star-s45-cf" size="xl"></xtyle-icon>
<!-- a functional glyph rides as a charge by its bare name -->
<xtyle-icon name="check-badge--circle-c3--check-s55-cf" size="xl"></xtyle-icon>
<!-- a negative-space die: three pips knocked out of a rounded face -->
<xtyle-icon name="dice-3--square3-c1--dot-p7-s14-ko--dot-s14-ko--dot-p3-s14-ko" size="xl"></xtyle-icon>
<!-- the same spec re-skinned by scheme -->
<xtyle-icon name="chip--hex-c1--dot-s30-c2" colors="statuses" size="xl"></xtyle-icon>
<script lang="ts">
import { Icon } from "@xtyle/svelte";
</script>
<Icon name="crest--shield-c1--star-s45-cf" size="xl" />
<Icon name="chip--hex-c1--dot-s30-c2" colors="statuses" size="xl" />
---
import Icon from "@xtyle/astro/Icon.astro";
---
<Icon name="crest--shield-c1--star-s45-cf" size="xl" />
<Icon name="chip--hex-c1--dot-s30-c2" colors="statuses" size="xl" />
A mark that carries its own palette
The palette reaches a mark from two places and the narrower one wins: colors sets it on the control, a ---ps-{palette} finish pins it in the name. A pinned mark is self-contained, so it colors the same wherever it lands; a pc override still beats the pinned palette for the slot it names.
<!-- the control sets the palette: both marks read the same scheme -->
<xtyle-icon name="chip--hex-c1--dot-s30-c2" colors="skittles" size="xl"></xtyle-icon>
<!-- a ---ps finish pins the palette in the NAME, so it wins over colors="…" -->
<xtyle-icon name="chip--hex-c1--dot-s30-c2---ps-thermal" colors="skittles" size="xl"></xtyle-icon>
<!-- a self-contained mark colors the same wherever it lands, with no control to wire -->
<xtyle-icon name="heat--ring-c1--dot-s28-c9---ps-thermal" size="xl"></xtyle-icon>
<!-- a pc override still beats the pinned scheme, for the one slot it names -->
<xtyle-icon name="chip--hex-c1--dot-s30-c2---ps-skittles--pc1-accent" size="xl"></xtyle-icon>
<script lang="ts">
import { Icon } from "@xtyle/svelte";
</script>
<Icon name="chip--hex-c1--dot-s30-c2" colors="skittles" size="xl" />
<Icon name="chip--hex-c1--dot-s30-c2---ps-thermal" colors="skittles" size="xl" />
<Icon name="heat--ring-c1--dot-s28-c9---ps-thermal" size="xl" />
---
import Icon from "@xtyle/astro/Icon.astro";
---
<Icon name="chip--hex-c1--dot-s30-c2" colors="skittles" size="xl" />
<Icon name="chip--hex-c1--dot-s30-c2---ps-thermal" colors="skittles" size="xl" />
<Icon name="heat--ring-c1--dot-s28-c9---ps-thermal" size="xl" />
Text marks
The letter primitive typesets a glyph as a mark, drawn in an indexed theme font slot (f0 sans, f1 display, f2 mono); a ---f finish swaps a slot for another theme font or a named web font. A named family is gated on its character set (letters, digits, spaces, hyphens, under a length cap), which is what keeps an untrusted icon name from carrying a quote or an angle bracket into the url() and the href the family lands in; iconFontImports then hands back ready-made loading code. Install the Google Fonts list with useGoogleFontCatalogue — as xtyle.dev's icon builder does — and the same calls sharpen: canonical casing (ibm+plex+mono becomes the real IBM Plex Mono, not the mis-cased guess that 404s), and an honest "no such family" with a did-you-mean instead of a snippet that quietly loads nothing. loadGoogleFont loads a family for preview, and embedFontsInSvg bakes it into an export (subsetted to the glyphs drawn), so the mark renders in the right face on a machine that has never heard of the font. Both reach Google only when called, since the fetch discloses the visitor's IP address.
<!-- the letter primitive typesets a glyph; -f1 draws it in the display font -->
<xtyle-icon name="initial--letter-A" size="xl"></xtyle-icon>
<xtyle-icon name="mono--letter-7-f2" size="xl"></xtyle-icon>
<!-- a monogram badged, colored, and scaled like any primitive -->
<xtyle-icon name="badge--circle-c1--letter-A-cb-s55" size="xl"></xtyle-icon>
<!-- a ---f finish swaps a slot to a named web font; the mark declares it but does not carry it -->
<xtyle-icon name="mark--letter-Q-f1---f1-noto+sans+symbols" size="xl"></xtyle-icon>
<!-- load it with the snippet iconFontImports() hands you rather than a hand-built URL -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols&display=swap">
<script lang="ts">
import { Icon } from "@xtyle/svelte";
import { embedFontsInSvg, iconFontImports, loadGoogleFont, resolveIconMark } from "@xtyle/core";
const name = "mark--letter-Q-f1---f1-sigmar";
// which families the mark needs, and the glyphs it draws in each
const fonts = iconFontImports(resolveIconMark(name)!.composition);
// nothing reaches Google until this runs: the fetch discloses the visitor's IP to Google
const preview = () => Promise.all(fonts.filter((f) => f.google).map((f) => loadGoogleFont(f.family)));
// an export must CARRY the font, or it renders in whatever face the viewer's machine substitutes;
// &text= subsets it to just the glyphs drawn, so a letter mark embeds a few KB, not a whole face
const download = async (svg: string) =>
embedFontsInSvg(svg, fonts.filter((f) => f.google).map((f) => ({ family: f.family, text: f.glyphs })));
</script>
<Icon name="initial--letter-A" size="xl" />
<Icon name="badge--circle-c1--letter-A-cb-s55" size="xl" />
<Icon {name} size="xl" />
<button onclick={preview}>Load from Google Fonts</button>
---
import Icon from "@xtyle/astro/Icon.astro";
import { iconFontImports, resolveIconMark } from "@xtyle/core";
const name = "mark--letter-Q-f1---f1-sigmar";
// null googleLink means Google does not serve that family — it will render in a fallback
const fonts = iconFontImports(resolveIconMark(name)!.composition).filter((f) => f.google);
---
{fonts.map((f) => <link rel="stylesheet" href={f.googleHref} />)}
<Icon name="initial--letter-A" size="xl" />
<Icon name="badge--circle-c1--letter-A-cb-s55" size="xl" />
<Icon name={name} size="xl" />