Inline SVGs in React + Webpack

| Megan Pearson

SVGs are awesome. They are super sharp and scalable. They can be manipulated by CSS and/or JavaScript. They're accessible to screen readers and assisitve devices. They're particularly cool and fun and easy to work with when you combine them with reusable React components.

I created a method to do SVG icons in a single React component like this...

<Icon name="arrow" fill="pink" />
<Icon name="unicorn" fill="purple" />

because I wanted to...

  • leverage the benefits of inline SVGs with the reusability of React components
  • easily style and modify SVGs on the fly
  • avoid having large chunks of SVG code directly in my React components
  • avoid having to import a bunch of SVGs in various components all the time
  • reuse the same component for all of my icons
  • be able to take updated files from designers and just drop them in instead of having to make a bunch of edits

The Process

I started off by making the SVG itself a component (ex: <ArrowIcon />) that I could pass properties to in order to style it inline. For example, I had an arrow icon arrow-icon that I wanted to reuse and change its color on the fly (maybe to neon-yellow arrow-icon) by doing:

<ArrowIcon fill="#BCC614" />

This is great, but what if we want to use a different icon (say, a unicorn unicorn) in our Dashboard component? It's pretty redundant to make a whole component, exactly like <ArrowIcon />, just to change the icon.

So! I made my reusable <ArrowIcon /> more flexible by also passing in a name property. This would correlate to the name of the SVG and be used to pick the right icon. For example:

<Icon name="arrow" fill="#00ABB0" />
<Icon name="accepted" fill="#E89B5B" />
<Icon name="unicorn" fill="#B05BE8" />

Gotchas

There are some ramifications to this method. Most importantly, because we load all of the SVGs into one component, there can be some issues if you have implemented code splitting. This could cause the SVGs to be duplicated across bundles. It's probably fine if there's just a handful of small SVGs, but if there are a lot and they're big - it could bloat your code. Therefore, this method is best suited for small to medium sized projects.

Additionally, if there are fill (or other) attributes in the SVG files, they may take precedence due to specficity. Therefore, you might need to remove them from the SVG itself and account for them in the component instead.

This method will load all of the SVGs regardless if they are in use or not. Consider if this is acceptable for your project.

Make sure the SVGs are properly loaded

Install the react-svg-loader and configure in webpack.config.js. For example:

{
  module: {
    rules: [
      ...,
      {
        test: /\.svg$/,
        use: [{
          loader: 'react-svg-loader',
        }],
      },
      ...,
    ]
  }
}

Make a component for just ONE icon

Create a functional component that just returns the arrow icon arrow-icon with a property called "fill". This is what will allow us to change the icons' color from our other components. In this example I have set a default color (an exciting, neon-green) if nothing is specified from the other components.

import React from 'react';
import Arrow from './arrow.svg';

const ArrowIcon = ({fill = '#5BE8AE'}) => {
  return <Arrow fill={fill} />;
};

export default ArrowIcon;

Ta da! This is your reusable, inline SVG component. You can use it in any other component by importing it and change its color by setting a fill property. For example, if we want an arrow icon in our <Dashboard /> component we would use it as follows:

import React from 'react';
import ArrowIcon from '../ArrowIcon';

const Dashboard = () => {
  <div>
    Some text with an arrow next to it <ArrowIcon fill="#666" />
  </div>;
};

export default Dashboard;

Woo! Inline SVG component! But, now we want to use that unicorn icon arrow-icon. So, instead of making an entirely different component just to change the icon, let's modify our existing <ArrowIcon /> to be more flexible and reusable for any icon.

Make a component for ANY icon

To make the existing <ArrowIcon /> more flexible, let's start by renaming it to <Icon />. Then we will modify it as follows:

import React from 'react';
import PropTypes from 'prop-types';

/* import all of the icons that you want to use */
import Arrow from './arrow.svg';
import Accepted from './accepted.svg';
import Rejected from './rejected.svg';
import Unicorn from './unicorn.svg';

/* create a function that will pick which icon to use */
const pickIcon = name => {
  switch (name) {
    case 'arrow':
      return Arrow;
    case 'accepted':
      return Accepted;
    case 'rejected':
      return Rejected;
    case 'unicorn':
      return Unicorn;
    default:
      throw new Error('no SVG for: ' + name);
  }
};

/* pass the name & fill props (that we will specify in our 
other components) to Icon to pick the right icon */
const Icon = ({name, fill = '#5BE8AE'}) => {
  const SVG = pickIcon(name);
  return <SVG fill={fill} />;
};

/* set the propTypes so we can catch bugs with typechecking */
Icon.propTypes = {
  name: PropTypes.string.isRequired,
  fill: PropTypes.string,
};

export default Icon;

Beautiful. Now we can specify which icon we want as a property in our components and style it as well. For example, back in <Dashboard/> :

import React from 'react';
import Icon from '../Icon';

const Dashboard = () => {
  <div>
    <span>
      Text with an arrow <Icon name="arrow" fill="#666" />
    </span>
    <span>
      Here is our majestic <Icon name="unicorn" />
    </span>
  </div>;
};

export default Dashboard;

There we have it.. a reusable, self-contained, inline SVG React component. :)