SVG sprites
A sprite is one SVG file with a symbol per icon. Pages reference the symbols with <use>, the browser downloads and caches the file once, and no JavaScript runs at all.
Get the sprite
The package ships a sprite with every icon at glyphfox/sprite.svg. Each icon is a <symbol> with the id gf- plus the icon name, its own viewBox and the stroke settings of the set.
npm install glyphfox cp node_modules/glyphfox/dist/sprite.svg public/icons/sprite.svg
<svg xmlns="http://www.w3.org/2000/svg"> <symbol id="gf-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">...</symbol> <symbol id="gf-search" viewBox="0 0 24 24" ...>...</symbol> ... </svg>
With a bundler you can let it copy and fingerprint the file instead. In Vite, the ?url suffix returns the final URL of the asset:
import spriteUrl from 'glyphfox/sprite.svg?url'; export const icon = (name) => `<svg class="icon" width="24" height="24" aria-hidden="true"><use href="${spriteUrl}#gf-${name}"></use></svg>`;
The full sprite holds all 419 icons in 117.9 KB, or 15.7 KB with gzip. A sprite with the 30 icons a typical app shell uses is 7.5 KB, 1.3 KB with gzip; the CLI builds one for you, as shown below.
Reference a symbol
Write an outer svg with the size you want and a use element that points at the symbol. The symbol brings its own viewBox, so the outer element needs only width and height.
<button type="button" class="btn"> <svg class="icon" width="20" height="20" aria-hidden="true"> <use href="/icons/sprite.svg#gf-download"></use> </svg> Download </button>
.icon { width: 1.25em; height: 1.25em; flex-shrink: 0; vertical-align: -0.2em; } .btn:hover .icon { color: #ff7a1a; }
The symbols draw with stroke="currentColor", and color inherits into the use content, so the icon follows the text color of its parent and any color you set on the outer svg. Width and height in CSS override the attributes, which lets the size follow the font size.
Same origin only
Browsers only follow a use reference to a file on the same origin as the page. A sprite on a CDN such as jsDelivr, or on another subdomain of your site, renders nothing, and CORS headers do not change that. Serve the sprite from your own origin, or fetch it once and put it in the page:
// jsDelivr sends CORS headers, so fetch works where <use> across origins does not. const res = await fetch('https://cdn.jsdelivr.net/npm/glyphfox@1/dist/sprite.svg'); const holder = document.createElement('div'); holder.setAttribute('aria-hidden', 'true'); holder.style.cssText = 'position:absolute;width:0;height:0;overflow:hidden'; holder.innerHTML = await res.text(); document.body.prepend(holder); // now <use href="#gf-home"></use> works anywhere on the page
Inline the sprite in the page
An inline sprite is the same symbols placed once in the HTML, usually right after <body>. References then use only the fragment, #gf-home, there is no extra request and the icons render with the first paint. The cost is that every page carries the symbols again, so inline a small subset rather than the full set.
<body> <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden"> <symbol id="gf-home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">...</symbol> <symbol id="gf-search" viewBox="0 0 24 24" ...>...</symbol> </svg> <a href="/"><svg class="icon" aria-hidden="true"><use href="#gf-home"></use></svg> Home</a> </body>
Hide the sprite with zero size and absolute positioning, not with display:none. Some browsers stop rendering masks that live inside a hidden SVG, and the solid style uses masks to cut its inner lines.
Build a subset with the CLI
glyphfox build writes a sprite with only the icons you list, rendered with the props you pass. Names can come from the command line or from a file with one name per line; spaces, commas and # comments are allowed in the file.
npx glyphfox build home search settings bell user --type sprite --out public/icons npx glyphfox build --from icons.txt --type sprite --weight 1.5 --out public/icons npx glyphfox build --from icons.txt --type sprite --variant solid --out public/icons/solid
# app shell home, search, settings, bell, user menu x chevron-down # badges and fusions work too file+check cloud&lock:overlap
Plain names keep their id: home becomes gf-home. Specs with a badge or a fusion get an id with the symbols replaced: file+check becomes gf-file_check and cloud&lock:overlap becomes gf-cloud__lock-overlap. Open the generated file once to copy the ids you need.
Building at the size you display also gets you the optical compensation of the components: --size 16 writes the slightly heavier stroke that glyphfox uses for 16 pixel icons, so a sprite for dense tables looks as strong as the rest of the interface.
The same command with --type css writes a stylesheet of mask classes instead (<span class="gf gf-home">), for sites that want icon classes without Tailwind CSS. The Tailwind CSS guide explains how mask icons behave.
Build options
A sprite is static, so the shared props become build flags. These are the ones that matter for a sprite; every flag has the same meaning as the prop with that name in the props table.
| Flag | Effect on the symbols |
|---|---|
--type sprite | writes sprite.svg (the default type); css and package are the other types |
--out DIR | output directory, the current directory by default |
--from FILE | reads names from a file |
--weight N | stroke weight from 1 to 3, written into each symbol |
--size N | the pixel size you display, for the optical stroke compensation below 24 |
--corners sharp | sharp joins and caps |
--variant duotone, --variant solid | the style of every symbol |
--color C, --secondary-color C | fixed colors instead of currentColor |
--secondary-opacity N | opacity of the duotone layer or the solid fill |
--rotate DEG, --flip-x, --flip-y | bakes a rotation or a mirror into the symbols |
What CSS can and cannot change
What use draws is a copy of the symbol. CSS on the outer svg or on the use element reaches that copy only through inheritance, which is why color works. Properties that the symbol sets itself do not: each symbol carries stroke-width, stroke-linecap and stroke-linejoin as attributes, and those win over any value inherited from outside. Setting stroke-width on .icon has no effect.
Decide weight, corners and style when you build the sprite, and build two sprites if you need two weights. When you need per instance control over weight, a second color or motion, render inline SVG with a component or toSvg instead.
Whole icon animations still work, because they act on the outer element: .icon.spin { animation: spin 1s linear infinite } rotates a sprite icon like any other element.
Accessibility
A sprite icon is an outer svg that you write, so its semantics are yours to set. Hide decorative icons with aria-hidden="true". For an icon that carries meaning on its own, make the outer element an image with a name:
<svg class="icon" role="img" aria-label="Payment failed"> <use href="/icons/sprite.svg#gf-circle-alert"></use> </svg>
Icon only buttons need the label on the button, as in the accessibility guide.
Caching
An external sprite is one request that every page shares. Serve it with a long cache lifetime and a fingerprint in the file name, such as sprite.3f9a1c.svg, so a new build changes the URL instead of waiting for caches to expire. Bundlers do this for you when you import the file. Each distinct sprite URL is downloaded once per page, no matter how many icons reference it.
Pitfalls
- A sprite on another origin
- An external
usepointing at a CDN or another subdomain renders an empty box; the browser only reports the blocked request in the console. Host the file on the page origin or inline it. - Duotone layers in the full sprite
- The 42 icons that have a hand drawn duotone layer keep it in
glyphfox/sprite.svg, as a translucent fill in the current color. If you want pure outlines, build a subset: the defaultoutlinestyle leaves the layer out, and--variant duotonekeeps it. - Masks in hidden or external sprites
- Solid symbols use a
maskto cut their inner lines. Masks can fail to render when the sprite is hidden withdisplay:none, and support for masks inside an external sprite file differs between browsers. Test the solid style in your target browsers, or inline that sprite. - Living states in a sprite
- Presets such as
spinordraware scoped to a class on the rootsvg, which a symbol does not keep, so--animatehas no visible effect in a sprite. Animate the outer element with your own CSS, or use inline SVG. - Ids that clash
- An inline sprite puts ids such as
gf-homeinto the page. Two inline sprites with the same icon, or your own element with the same id, make references ambiguous. Inline one sprite per page.
Questions
When is a sprite better than components?
On server rendered or static sites without a JavaScript framework, and on pages that repeat many icons: the symbols are downloaded once and every use is a few bytes of markup. Components win when icons change with state, morph, animate or need a second color, because a sprite is fixed at build time.
Can I reference the sprite on jsDelivr directly?
Not with use, because browsers block external references to other origins. Fetch the file and insert it into the page, as shown above, or copy it to your own server.