React Christmas

How to use SVGs in React

A 5 minute read written by
Mira Thoen Feiring
18.12.2018

Previous postNext post

SVGs are here to stay, and React seems to be sticking around for a while as well. So how do you go about combining them?

Even though IE 11 still struggles a bit with properly scaling SVG files, SVGs are now supported by all modern browsers. That is, at least according to Can I Use. To cite Jake Giltsoff:

Scalable Vector Graphics can look crisp at all screen resolutions, can have super small file sizes, and can be easily edited and modified.

So why use any other image format, ever? Well, except for the times when you need an actual image of course. But let's leave the "why" with this brief introduction, and jump straight into the "how".

How to use an SVG in a React app, slightly depends on the rest of your website architecture, but most importantly whether the SVG should respond to any kind of interactions. Let's start with the simplest example:

Static SVGs

The easiest way of implementing a static SVG in a React app is like this:

const App = () => <img src="/path/image.svg" alt="" />;

After all, the img tag simply refers to an image, either through an absolute or a relative path. However, SVGs are often pretty small, and in these cases we are usually better off inlining the images as Data URLs, limiting the amount of requests a browser has to make to the server in order to render the webpage.

To transform an image into a Data URL, we need an appropriate loader in our bundler. I'm going to be using loaders written for webpack in my examples, but alternatives should be available for most other popular bundlers. For transforming files into Data URLs, the most common webpack loader is url-loader, which can be added in the following manner

Install as a devDependency:

$ npm i --save-dev url-loader

Add to webpack.config.js:

rules: [
    {
        test: /\.(png|jpg|gif|svg)$/i,
        use: [
            {
                loader: 'url-loader',
                options: {
                    limit: 10000,
                },
            },
        ],
    },
];

Import the file in your React app:

import image from 'path/image.svg';

const App = () => <img src={image} alt="" />;

The output in the DOM will be something like this

<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbm..." alt="" />

Now, url-loader actually transforms the images into base64 URIs. Seeing as an SVG is a human-readable xml string, using base64 encoding is not mandatory. Instead, we can use the more explicitly named svg-url-loader, which loads SVG files as an UTF-8 encoded Data URL string. The benefits of this are according to the README:

  1. Resulting string is shorter (can be ~2 times shorter for 2K-sized icons);
  2. Resulting string will be compressed better when using gzip compression;
  3. Browser parses utf-8 encoded string faster than its base64 equivalent.

In the example above, I defined a limit in the options object of the loader. This tells webpack to only encode images that does not exceeds 10 kB in size. Both url-loader and svg-url-loader uses file-loader as a fallback, for files that exceeds this limit. What file-loader does, is basically to create a copy of the image file in your output directory. So if the SVG in the example above had exceeded 10 kB, the output in the DOM would look more like this:

<img src="ebbc8779488b4073081931bd519478e8.svg" alt="" />

This can be a bit cryptic though, so in order to get a more sensible naming of files in your output directory, you can add name: '[path][name].[ext]' to the loader options, like this:

rules: [
    {
        test: /\.svg$/,
        use: [
            {
                loader: 'svg-url-loader',
                options: {
                    limit: 10000,
                    name: '[path][name].[ext]',
                },
            },
        ],
    },
];

Notice how I used svg-url-loader in this example, and also limited the test regex to only include .svg.

These loaders also work if you want to use SVGs as background-image or content in CSS. Fun fact though: Beware of using background-size if you intend to change background-image on hover or focus. Safari doesn't like this very much.

Static, but animated, SVGs

Say you have an animated spinner, or some other kind of fancy loader, created in SVG. You might think that you would need to inline this SVG, in order to animate its properties with CSS. Good news: You don't!

It is true that an SVG loaded as an image, as described above, can't be modified from "the outside". However, as long as the animation is either applied as CSS within the SVG itself (with a <style> element), or with an SVG <animate> element, this animation will run however the SVG is implemented on the page.

Interactive SVGs

Now, to the really fun part! If you want to have full control of your SVGs from within your app, and how they respond to different interactions, inlining them is the way to go. In theory, you don't necessarily need a loader for this, you could just copy the SVG code directly into a component. There are a few reasons why this might not be the best way to go though. Say you were to change some part of the SVG through a visual editor, such as Sketch or Adobe Illustrator. Wouldn't it be decidedly easier to just switch the SVG file, and not have to copy paste over the SVG code within the respective component? In order to avoid this, you can use react-svg-loader:

rules: [
    {
        test: /\.inline.svg$/,
        loader: 'react-svg-loader',
    },
];
import Image from 'path/image.svg';

const App = () => <Image width={50} height={50} />;

Notice how I prefixed the test regex with inline. This is because you likely don't want all SVGs in your app to be inline. Say you were to only have this loader, and then used an SVG as a background-image. Your resulting CSS would look something like this:

background-image: url(functionImage(props){returnReact.createElement(
        'svg',
        props,
        React.createElement(...)
    )});

So usually, we want to use both react-svg-loader and svg-url-loader. To avoid the latter trying to parse an SVG file supposed to be inline, we need to exclude them from the loader. In this case, where inline SVGs include inline before the file extension, the full set of loaders for SVGs would look like:

 {
    test: /^(?!.*\.inline\.svg$).*\.svg$/,
    loader: 'svg-url-loader',
    options: {
        limit: 10000,
        name: '[path][name].[ext]',
    },
},
{
    test: /\.inline.svg$/,
    loader: 'react-svg-loader',
}

Of course, you could also choose to put the files in different folders, and rather use the exclude and include options on each loader. Furthermore, there are a couple of other loaders that does more or less the same as the ones I've mentioned here. But hopefully this has been useful anyway.

Happy SVGing!

Read the next post