External SVGs that you can style
Tags: javascript, webdev, svg, css
Published: 2022-04-11 08:11:12

You can inline SVGs, use a symbol sprite, make it an `<img>`, an `<object>` even an `<iframe>` tag, you can use data URI, css backgrounds, filters and many more [techniques](https://cloudfour.com/thinks/svg-icon-stress-test/) just to show it to a user. But if you want to utilize all this fancy power of SVGs, you have to inline. Had to. Until now.

## Fancy power of SVGs
What i mean here is a possibility to manipulate SVG's styles from css. It is a must-have feature, especially for icons. You want to change the icon's fill on hover and you don't want to use a separate image for this.

To achieve this you either inline SVGs directly to your HTML, or you can use a separate file, a symbol sprite, that is also needs to be inserted to the DOM. No other technique allows you to do this. So let's look at inlining and sprites closer.

## Inlining
Inlining is the simplest and the most powerful way of using SVGs. You just place an SVG right to the HTML markup and it works.

```html
<span>
  <svg viewBox="0 0 16 16" width="16" height="16">
    <!-- svg content -->
  </svg>
</span>
```

You can then [style it with css](https://css-tricks.com/svg-properties-and-css/), change almost any property, even position and [animate specific parts](https://blog.logrocket.com/animating-svg-with-css-83e8e27d739c/) of it. But there is an obvious cons of doing so:

1. If the SVG is big, it takes additional time for the whole page to load.
2. When using in React with (great) tools like [SVGR](https://react-svgr.com/), it increases the javascript bundle size.
3. You can't actually control the loading process, like with basic images for example.

## Sprite
Another technique that allows to manipulate SVG from css is a sprite of `<symbols>`. Content of SVGs is stored in the separate hidden file, and with `<use>` tag you can reference the right symbol from where you want your image to appear.

```html
<!-- hidden sprite on the top of the page -->
<svg style="display:none">
  <symbol id="icon" viewBox="0 0 16 16" width="16" height="16">
    <!-- svg content -->
  </symbol>
</svg>

<!-- usage -->
<span>
  <svg><use href="#icon" /></svg>
</span>
```

The sprite may also be stored in the external file, if you don't care about IE11:
```html
<svg>
  <use href="https://cdn.net/sprite.svg#example"/>
</svg>
```

Unfortunately you can't set reference to separate SVG-file  in `<use>`, you only need a sprite, so this **won't work**:
```html
<svg>
  <use href="https://cdn.net/icon.svg"/>
</svg>
```

Fancy css-manipulation here works, but is limited. You don't have an access through the Shadow boundary, that `<use>` represents.

The good news is that cascading is working, so you can change the fill of your SVGs and use `currentColor` inside any property. It's just enough for the most of icons-related cases:

```html
<!-- sprite -->
<svg style="display:none">
  <symbol id="icon">
    <path fill="currentColor">
  </symbol>
</svg>

<!-- icon with class name -->
<span>
  <svg class="icon"><use href="#icon" /></svg>
</span>

<!-- css -->
<style>
  .icon {
    color: rebeccapurple;
  }
</style>
```

The bad thing about sprites is that they are not extendable. You'll end up generating huge files that you have to load on every page and you don't need 99% of it's content.

## Compromise
We obviously need a compromise here, a way to load only necessary SVGs, don't bother our javascript with rendering it and keep the possibility of css-styling.

And there is such, we can fetch! We need a tool that:
1. Fetches SVGs in browser. And caches them.
2. Puts them into sprite.
3. Provides an API to pass reference to this SVG to `<use>` tag.

The first one to think about this was [Chris Coyier](https://css-tricks.com/ajaxing-svg-sprite/) in 2015. But no one still have written a tool for this. So we did.

## Handy SVG
It is called **Handy SVG** and it lives on [github](https://github.com/ivliag/handy-svg) and publishes to npm. So you can:

```bash
npm i handy-svg
```

And then in your React code:
```typescript

import {HandySvg} from 'handy-svg';
import iconSrc from './icon.svg';

export const Icon = () => (
    <HandySvg
        src={iconSrc}
        width="32"
        height="32"
    />
);
```

Or if you don't use React:
```typescript
import {injector} from 'handy-svg/lib/injector';

const src = "https://cdn-server.net/icon.svg";

// Fetches svg content and puts it to sprite
injector.load(src);

// Gets the id of your svg in sprite
const id = injector.getId(src);

// Then you can use it at your will
const svg = `<svg><use href="#${id}" /></svg>`;
```

Of course it's a compromise, there are some limits, a bit of a FONI  (Flash of No Icons) for example. But still, from all the possibilities that we have, this is the most handy way to use SVG on the web right now.

---

The PR's are very welcome and I will be happy to hear any feedback.