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 (uniformly with s, then stretched per axis by sx/sy layered on top of it), 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). The library it draws from is mostly filled art rather than line work — curves and volumes and polygons you stack, so a cylinder capped with a disc is a database and a water level under a wave is the sea — and the finish resizes (---s/---sx/---sy), moves (---mx/---my), and re-centers (---center) the whole composite. 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 |
|---|---|---|---|---|
-- grammar, e.g. crest--shield-c1--star-s45-cf). A plain name that is neither renders a visible placeholder box. | ||||
c1–c9 color slots draw from (accents, skittles, statuses, thermal, severity, intensity), so one spec re-skins across palettes. A mark that pins its own palette in its name (a ---ps-{palette} finish) overrides this. Ignored for a functional glyph. | ||||
em off the surrounding text: xs, sm, md, lg, xl. Omit to match the current text size. The scale shares its floor with Button's, so an xs control can hold an xs glyph. When a layout constraint wants an exact figure rather than a step, set --xtyle-icon-size on the host and it wins over whichever step is named. | ||||
currentColor. | ||||
prefers-reduced-motion. | ||||
Appearance
Sizes
xs
The floor of the scale, matching an xs Button so the smallest control can hold a correctly-sized glyph.
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. This one renders light DOM, so the selector is the handle.
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--yellowAccessibility
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" />The primitive library
The library is mostly filled art rather than line work: solids you stack into a mark. Curves and nature (wave, water, swish, blob, lens, leaf, cloud, mountain, sun, flame, drop), volume (disc, cylinder, cone, half, quarter, wedge, oval, pill, egg, arch), polygons (pentagon, octagon, trapezoid, ramp, squircle, gem), markers (banner, tag, bubble, chevron, arrow), and stars (star4 / star6 / star8, burst, seal). cylinder is the square-edged oval a database icon is made of: stack a disc cap on one and you have the drum.
<!-- a database: the square-edged oval, capped with flat discs -->
<xtyle-icon name="database--cylinder-c1--disc-y-25-c3--disc-y-2-c2-a70" size="xl"></xtyle-icon>
<!-- water: a wavy fill level, clipped to a circle -->
<xtyle-icon name="sea--circle-cb--water-c1--circle-i-ko" size="xl"></xtyle-icon>
<!-- filled art stacks; there is no line work to line up -->
<xtyle-icon name="weather--sun-c4-x-18-y-16-s70--cloud-cf-s90-y8" size="xl"></xtyle-icon>
<xtyle-icon name="spark--seal-c2--star4-s45-cf" size="xl"></xtyle-icon><script lang="ts">
import { Icon } from "@xtyle/svelte";
</script>
<Icon name="database--cylinder-c1--disc-y-25-c3--disc-y-2-c2-a70" size="xl" />
<Icon name="sea--circle-cb--water-c1--circle-i-ko" size="xl" />---
import Icon from "@xtyle/astro/Icon.astro";
---
<Icon name="database--cylinder-c1--disc-y-25-c3--disc-y-2-c2-a70" size="xl" />
<Icon name="sea--circle-cb--water-c1--circle-i-ko" size="xl" />Sizing and moving: the layer and the whole mark
A layer sizes with s and moves with x/y; sx/sy then stretch that result on one axis, layering on s rather than replacing it, so one circle covers every ellipse ratio. The finish carries the same dials for the whole composite — ---s/---sx/---sy resize it and ---mx/---my move it, both wrapping the outline and shadow so a finished mark travels as one piece. ---center re-centers the mark on the ink it actually leaves standing: a knockout is the tool that carves ink rather than ink itself, so a carved mark seats on what survives the carve. The engine can do this anywhere because it measures the primitive geometry statically rather than asking a DOM.
<!-- sx / sy stretch one layer out of its authored proportion, on top of s -->
<xtyle-icon name="wide--circle-c2-sx150-sy60" size="xl"></xtyle-icon>
<xtyle-icon name="layered--circle-c2-s50-sx200" size="xl"></xtyle-icon>
<!-- the finish resizes and moves the WHOLE composite, outline and shadow with it -->
<xtyle-icon name="inset--square1-c1--star-s50-cf---s70" size="xl"></xtyle-icon>
<xtyle-icon name="nudged--square1-c1--star-s50-cf---mx14--my-14" size="xl"></xtyle-icon>
<!-- ---center re-centers on the ink left standing, so a carve moves where the mark sits -->
<xtyle-icon name="centered--star-p1-s40-c1---center" size="xl"></xtyle-icon>
<xtyle-icon name="seated--circle-c1--circle-x50-ko---center" size="xl"></xtyle-icon><script lang="ts">
import { Icon } from "@xtyle/svelte";
</script>
<Icon name="wide--circle-c2-sx150-sy60" size="xl" />
<Icon name="inset--square1-c1--star-s50-cf---s70" size="xl" />
<Icon name="centered--star-p1-s40-c1---center" size="xl" />---
import Icon from "@xtyle/astro/Icon.astro";
---
<Icon name="wide--circle-c2-sx150-sy60" size="xl" />
<Icon name="inset--square1-c1--star-s50-cf---s70" size="xl" />
<Icon name="centered--star-p1-s40-c1---center" 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" />