Frameworks

Icons in plain HTML from a CDN

One script tag gives a page every glyphfox icon, the <glyph-fox> element and a runtime that turns <i data-glyph> placeholders into inline SVG. No build step and no framework.

Add the script

The single file build carries every icon, the element, the placeholder runtime and the engine. jsDelivr and unpkg both resolve the bare package URL to it, dist/web/glyphfox.min.js.

index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My page</title>
  <script defer src="https://cdn.jsdelivr.net/npm/glyphfox@1"></script>
  <!-- or https://unpkg.com/glyphfox@1 -->
</head>
<body>
  <glyph-fox name="home"></glyph-fox>
  <i data-glyph="search" data-size="20"></i>
</body>
</html>

With defer the download does not block parsing. The script runs once the document is parsed, defines the element, upgrades every <glyph-fox> on the page and replaces the placeholders.

Pin the version

glyphfox@1 follows the latest 1.x release, which is convenient while you prototype. In production, pin the exact version and file, so the page never changes under you and the CDN can cache the file for good.

index.html
<script defer src="https://cdn.jsdelivr.net/npm/glyphfox@1.3.0/dist/web/glyphfox.min.js"></script>

An exact version can also carry Subresource Integrity: take the hash of that file, which jsDelivr lists for every file, and add integrity and crossorigin="anonymous" to the tag. A range such as @1 cannot carry a hash, because the file behind it changes with each release.

You can also self host it. Copy node_modules/glyphfox/dist/web/glyphfox.min.js into your public folder and point the tag at it: the file is self contained and makes no further requests.

The glyph-fox element

Use the element for icons that change: a name that switches, a morph, a hover or click trigger, a state. It reads every prop from its attributes and redraws when one of them changes.

menu.html
<button type="button" class="menu" aria-label="Open menu" aria-expanded="false">
  <glyph-fox name="menu" morph duration="300"></glyph-fox>
</button>

<script type="module">
  const button = document.querySelector('.menu');
  const icon = button.querySelector('glyph-fox');
  button.addEventListener('click', () => {
    const open = button.getAttribute('aria-expanded') !== 'true';
    button.setAttribute('aria-expanded', String(open));
    button.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
    icon.setAttribute('name', open ? 'x' : 'menu');
  });
</script>

The morph attribute animates the change from menu to x, one of the hand tuned pairs. Shadow DOM styling, custom tag names and triggers are covered in the web components guide.

Placeholders with data-glyph

For icons that never change, a placeholder is lighter than a live element. The runtime replaces every element that has a data-glyph attribute with inline SVG, copies its class to the svg, and keeps watching the page, so placeholders added later by your scripts are replaced too.

index.html
<i data-glyph="search" data-size="20"></i>
<i data-glyph="file+check@tr" data-weight="1.5"></i>
<i data-glyph="folder" data-variant="duotone" data-secondary-color="#ff7a1a"></i>
<i data-glyph="trash" class="danger" aria-label="Delete"></i>

Props are data attributes in kebab-case: data-size, data-weight, data-variant, data-modifier, data-animate and the rest of the table below. An aria-label on the placeholder becomes the title of the icon, so the svg gets an accessible name.

The replacement happens once. The placeholder is gone afterwards, so changing its data attributes later does nothing, and listeners attached to the <i> are lost with it. Attach listeners to a parent, or use the element when the icon has to change.

With a bundler, import glyphfox/runtime instead of the CDN file. It exports replaceGlyphs and runs it on load, and it includes every icon, since a page can ask for any name. Do not load that module straight from a CDN: it imports the package as separate ES modules, one file per icon, which costs hundreds of requests. The single file build already contains the same runtime.

The global API

The CDN build also sets window.glyphfox with the core API: toSvg, fuse, search, getIcon, iconNames, categories, createMorph, toDataUri, replaceGlyphs and more. It exists once the script has run, so call it from a module script placed after the tag, or after DOMContentLoaded.

icon-search.html
<input type="search" id="q" placeholder="Search icons" aria-label="Search icons">
<div id="results"></div>

<script type="module">
  const { search, toSvg } = window.glyphfox;
  const q = document.getElementById('q');
  const results = document.getElementById('results');
  q.addEventListener('input', () => {
    results.innerHTML = search(q.value, { limit: 24 })
      .map((icon) => `<button type="button" aria-label="${icon.name}">${toSvg(icon.name, { size: 28 })}</button>`)
      .join('');
  });
</script>

toDataUri turns an SVG string into a URL for CSS backgrounds or an <img>. An image cannot inherit currentColor from the page, so pass an explicit color.

checklist.js
const { toDataUri, toSvg } = window.glyphfox;
const check = toDataUri(toSvg('check', { size: 16, color: '#16a34a' }));
document.documentElement.style.setProperty('--icon-check', `url("${check}")`);
checklist.css
.done::before {
  content: '';
  display: inline-block;
  width: 16px;
  height: 16px;
  margin-right: 6px;
  background: var(--icon-check) no-repeat center / contain;
}

dist/web/glyphfox.esm.js is the same build as an ES module. Its default export is the API object, and like the classic file it defines the element and runs the placeholder runtime.

logo.html
<div id="logo"></div>

<script type="module">
  import glyphfox from 'https://cdn.jsdelivr.net/npm/glyphfox@1/dist/web/glyphfox.esm.js';

  document.getElementById('logo').innerHTML = glyphfox.toSvg('fox', { size: 48, variant: 'duotone' });
</script>

File size, and when to build instead

The single file holds every icon: 216.4 KB minified, 62.4 KB with gzip. Served from a CDN with long caching, that is a fair price for a prototype, a documentation site or a page that shows many different icons. It is a poor deal for a landing page that shows six.

ApproachWhat shipsGood for
One CDN scriptEvery icon, the element, the runtime and the API.Prototypes, docs, pages with many icons.
A bundler with glyphfox/web/elementThe element and the icons you register.Apps that already have a build step, see tree shaking.
CLI subset as CSSOne stylesheet with a mask class per icon and no JavaScript.Static sites and icons that never change.
CLI subset as a spriteOne SVG file with a symbol per icon.Server rendered HTML that repeats the same icons, see SVG sprites.
terminal
npx glyphfox build home search menu loader --type css --out public
npx glyphfox build home search menu loader --type sprite --out public/icons
index.html
<link rel="stylesheet" href="/glyphfox.css">

<span class="gf gf-home" aria-hidden="true"></span>
<span class="gf gf-loader gf-spin" aria-hidden="true"></span>

The CSS build draws each icon as a mask filled with currentColor, so it takes the text color and the font size, and it includes the gf-spin and gf-pulse animations. Every command takes the shared props as flags, such as --weight 1.5 or --variant duotone.

Attributes

The element and the placeholders read the same props, both in kebab-case: as plain attributes on <glyph-fox>, such as size and secondary-color, and with a data- prefix on placeholders, such as data-size and data-secondary-color. The element also takes layout, gap and morph.

AttributeTypeDefaultDescription
namestringnoneThe icon spec: a name, name+modifier@corner, a&b:layout or name/variant.
layoutoverlap, side, stack, grid, orbit, badge, insidenoneFusion layout when the name joins icons with &.
gapnumber1Knockout gap of a fusion, in grid units.
morphbooleanfalseAnimates every change of name as a morph.
sizenumber, string24Width and height; numbers are pixels.
colorstringcurrentColorMain color.
secondary-colorstringcolorColor of the duotone layer and of the solid fill.
secondary-opacitynumber0.3 / 1Opacity of the duotone layer or of the solid fill.
weightnumber2Stroke from 1 to 3 on the 24 grid, optically compensated below 24 px.
absolute-weightbooleanfalseKeeps the stroke at weight pixels at any size.
cornersround, sharproundRound or sharp joins and caps.
variantoutline, duotone, solidoutlineStyle axis; solid comes from the closed shapes.
rotatenumber0Rotation in degrees.
flip-xbooleanfalseMirror horizontally.
flip-ybooleanfalseMirror vertically.
mirror-rtlbooleanfalseMirror in right to left layouts.
animatedraw, pop, wiggle, spin, pulse, bounce, shakenoneLiving state preset.
triggermount, hover, click, statemountWhen the animation or the morph plays.
activebooleanfalseThe state for trigger state.
durationnumber (ms)per presetAnimation or morph duration.
delaynumber (ms)0Animation or morph delay.
loopboolean, numberfalseRepeat forever or a number of times (spin and pulse loop by default).
modifierplus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dotnoneBadge with an automatic knockout gap.
modifier-cornertop-left, top-right, bottom-left, bottom-rightbottom-rightCorner of the badge.
morph-toname, datanoneMorph target; the trigger decides when it shows.
titlestringnoneAccessible name: title, role img and aria-label.
decorativebooleantrue without a titleHides the icon from assistive technology.

Pitfalls

Scripts that run too early
An inline classic script runs during parsing, before a deferred CDN script, so window.glyphfox is still undefined there. Use type="module", since module scripts run after deferred scripts in document order, or wait for DOMContentLoaded.
The runtime module from a CDN
dist/runtime/auto.js is meant for bundlers. Loaded directly in the browser it pulls the package one module at a time, a file per icon. Use the single file build on plain pages.
Placeholders are replaced once
Changing data-* attributes after the replacement has no effect, and listeners on the placeholder are dropped. Use <glyph-fox> for icons that change.
Self closing elements
<glyph-fox name="home" /> is an open tag in HTML, so the content after it disappears inside the element. Always write the closing tag.
currentColor in images
SVG used as an <img> source or a CSS background cannot see the page color. Pass a color to toSvg, or use the CSS subset, which works with masks.
Click triggers on placeholders
Placeholders become static SVG. Hover morphs and animations still play, because they are CSS inside the SVG, but click and state triggers need the element, which switches the states for you.
Floating versions in production
glyphfox@1 moves with every 1.x release, and a changed file breaks an integrity hash. Pin glyphfox@1.3.0 and the full file path.

Questions

Should I use the element or data-glyph placeholders?

Placeholders for icons that never change after load: they turn into plain inline SVG with no live element behind them. The element for icons that switch names, morph, or react to hover, click or state. Both come from the same script and can be mixed on one page.

Does it work with HTML swapped in later, for example by htmx?

Yes. The runtime observes the whole document, so placeholders inside inserted HTML are replaced as they arrive, and <glyph-fox> elements upgrade on insertion because the element is defined for the page.

Icons in this guide