Inline, sprite or component: how to ship SVG icons
What is the best way to ship SVG icons in a web app?
Ship them as components that render inline SVG, and let tree shaking drop the ones you do not use. A sprite is better when the same few icons repeat many times on a page. Never use img or a CSS background if you want the icon to follow your text colour.
There are four ways to put an SVG icon on a page. They differ on one property that decides most cases: whether the SVG is in your document.
If it is, the icon can inherit currentColor, be styled by your CSS, and be animated. If it is not, it is a separate document with none of that.
The four options
Inline SVG in the markup
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 20 L12 4 L20 20"/>
</svg>
Full control, no request, works everywhere. The cost is bytes in the HTML, repeated for every use, and none of it cached separately.
Fine for a handful of icons. Poor when the same icon appears forty times in a table.
A sprite with <use>
One inline <symbol> block, referenced by id:
<svg hidden><symbol id="i-trash" viewBox="0 0 24 24"><path d="..."/></symbol></svg>
...
<svg><use href="#i-trash"/></svg>
Each use costs about thirty bytes. currentColor still works, because the sprite is in the same document. This is the right answer when a small set of icons repeats heavily.
The catch is that the sprite must hold every icon the page might need, so it is a fixed cost paid up front.
A component
import { TrashCan } from '@your/icons';
<TrashCan aria-hidden="true" />
The component renders inline SVG. The bundler drops every icon you did not import, so you pay only for what you use. This is the default answer for a component based app.
Make sure the package sets "sideEffects": false and ships one module per icon, or tree shaking will not work and you will bundle all 3000.
<img> or a CSS background
<img src="/icons/trash-can.svg" alt="">
Cached well, trivially simple, and the icon cannot follow your text colour. It is a separate document, so currentColor resolves to black and your CSS cannot reach inside.
Use it for a large decorative graphic. Do not use it for an icon set.
The comparison
| Bytes per use | currentColor | Cached separately | Tree shakes | |
|---|---|---|---|---|
| Inline | Full icon | Yes | No | Not applicable |
Sprite <use> | About 30 | Yes | Yes, one file | No |
| Component | Full icon, once per icon | Yes | With the bundle | Yes |
<img> | About 40 | No | Yes, per file | Not applicable |
The CSS mask escape hatch
If you must style an icon from CSS alone, a mask keeps the colour control that background-image loses:
.icon-trash {
width: 24px; height: 24px;
background-color: currentColor;
mask: url(/icons/trash-can.svg) center / contain no-repeat;
}
The background colour shows through the shape of the SVG. It works in every current browser and is the only way to get a coloured icon from a separate file.
What we ship
Our set ships the geometry only, as data:
{ name: 'trash-can', phrase: 'trash can', svg: '<path d="..."/><path d="..."/>' }
The renderer supplies the wrapper. That gives you every option above from one source: build a sprite, render a component, or write the file out. It also means the stroke contract is one line, so changing it does not touch 3000 files. See currentColor for why the wrapper matters.
Questions people ask
Should I inline the SVG or use a sprite for a table with many rows?
A sprite. Forty inline copies of a 300 byte icon is 12 KB of repeated markup; forty <use> references is about 1 KB plus one copy.
Does an icon font still make sense?
No. Icon fonts break when the font fails to load, they are read as text by some screen readers, and they force single colour glyphs onto a text baseline. SVG solves all three.
How do I stop a component library from bundling every icon?
Ship one module per icon, set "sideEffects": false, and import named exports rather than the package root. Then check the built bundle to confirm; the configuration is easy to get wrong silently.
Are inline SVGs bad for HTML size?
Only if repeated. A page with sixty distinct icons inline carries maybe 20 KB before compression, and SVG paths compress well. Repetition is the problem, not inlining.