Skip to content
Vitops
GuidesComponentsPackagesThemeReferenceChangelog

Start here

VitopsInstallationYour design systemYour config

Live preview

Theme previewAnimation libraryIconsPatterns

Components

OverviewCSS classesWeb componentsAstro componentsBricks elements

Packages

@getvitops/cli@getvitops/astro@getvitops/vite@getvitops/generator@getvitops/core@getvitops/utils@getvitops/emdash@getvitops/create

Reference

Config referenceOutput formatsColour systemType & space scalesComponent patternsIconsConsent gateConversion trackingSearchLegal documentsCSS class vocabularyBricks elements

Releases

Changelog

Icons

Semantic icon names that resolve per configured set, how the bundle is derived on a server build, and the three delivery paths (astro-icon, astro-iconset, SVG sprite).

Generated page. Rendered from this project’sdesign-system.json by @getvitops/generator — the same bundlevitops docs prints against your own config. Don’t edit it by hand.

Declaring themWhy the bundle list existsDeliveryMarkup

Icons are named by meaning, not by set. menu resolves to fa7-solid:bars, lucide:menu or ph:list depending on which set the site configures, so changing sets is a config edit rather than a find-and-replace across templates.

The escape hatch is deliberate and always available: a name containing : passes through untouched. <Icon name="simple-icons:zoho" /> renders that exact glyph whether or not the semantic map knows it.

Declaring them

The icons block lives in the site config, not design-system.json — it describes what a site uses, not what the design system defines.

{
  "icons": {
    "ui": "ph",
    "brand": "simple-icons",
    "weight": "bold",
    "semantic": ["menu", "close", "arrow-right"],
    "simple-icons": ["zoho", "cloudflare"],
    "sprite": true
  }
}

ui and brand pick which set a semantic name resolves against. Any other key is an iconify collection name with an explicit list, passed through verbatim.

weight is only meaningful for sets that encode weight in the icon NAME. Phosphor does (list, list-bold, list-fill), so ph accepts it; Font Awesome splits weights across collections instead (fa7-solid vs fa7-regular), so there the weight is part of the prefix and this key is ignored.

Why the bundle list exists

astro-icon is zero-config on a static build. Under output: 'server' it bundles every icon in a set unless given an include map — which is why projects end up hand-maintaining one.

The icons option on vitops() derives it by scanning your source, merged with whatever you declare. On a static build no include is passed at all: there is nothing to trim there, and a list could only drop a glyph the scan couldn’t see.

Two failure modes, treated differently on purpose:

  • A name you declared that doesn’t resolve is a config error and throws.
  • A name only the scan found and can’t resolve just warns — a bare unmapped name is more often a local src/icons/*.svg than a mistake, and a dev server that dies on a template typo is worse than one that tells you.

Names computed at runtime (<Icon name={expr} />) can’t be read statically. They are reported with file and line, never guessed at — declare them in semantic or a per-set list. Run vitops icons to see the whole report.

Delivery

Path Used by Needs
astro-icon / astro-iconset Astro sites the integration, an optional peer
SVG sprite (icons.svg) Bricks/WordPress, EmDash renderers, plain HTML icons.sprite: true

The sprite is the no-JavaScript path: <svg><use href="/vitops/icons.svg#ph--list"></svg>. Ids are the qualified name with : replaced by -- (: is not valid in a fragment identifier), plus a set-independent icon-<name> alias for every semantic name — so sprite markup survives an icon-set change the same way resolveIcon protects Astro call sites.

In WordPress, vitops_icon('menu') and [vitops_icon name="menu"] emit that markup, and a Vitops → Icon Bricks element wraps the same helper.

⚠️ An external-file <use> is same-origin only and dead under file://. Serving dist/ from a different origin makes every icon vanish with no console error.

Markup

Always the framework’s .icon box:

<span class="icon" aria-hidden="true"><svg>…</svg></span>

.icon sizes in em via --icon-size (default 1.25em) and sets fill: currentColor, so an icon inherits the colour of whatever it sits in. Because .cta and :where(button, .btn) are already inline-flex with a gap, a start or end icon is child order — not a modifier class.

Icons are aria-hidden by default. Pass a label only when the icon carries meaning on its own; beside a text label it would be announced twice.

Note .icon-mask is a different thing: the CSS-only adornment path used by .link--icon::before, where the glyph is a mask-image on a pseudo-element and there is no markup to hang an <svg> on.

Built with the design system it documents.

@getvitops on npm