# Inline, sprite or component: how to ship SVG icons

> Four ways to deliver an SVG icon set, and the one property that decides between them. Includes what each costs in bytes, caching and colour control.

URL: https://semanticicons.com/blog/how-to-ship-svg-icons
Published: 2026-10-21
Updated: 2026-10-21

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

```html
<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:

```html
<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

```jsx
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

```html
<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:

```css
.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:

```ts
{ 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](/blog/svg-currentcolor-icons) 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.
