Skip to content

Icon Library in React: Why Inline SVG Are Better than a Font

March 25, 2021Guillaume Égée11 min read

Inline SVG vs. font

How I managed to choose a performant icon system adapted to a Next.js project with styled-components.

Almost every website uses icons in order to quickly guide users or to improve the aesthetics of the page.

Usually, designers can export icons to SVG format. SVG stands for Scalable Vector Graphics which means they can grow indefinitely, as they are defined by a description, not by pixels like in pictures.

In a new Next.js/React project with a hundred icons, I had to choose the best way to integrate icons. In this article, I propose to:

So many ways to integrate SVG icons!

Once you have an SVG (let's say a wonderful arrow icon), there are at least four ways to import them... not all of these methods are worth it!

SVG as an image

An SVG can be used as the source of an img HTML tag:

const Icon = ({ src, ...imgProps }) => <img src={src} {...imgProps} />;

const ArrowIcon = <Icon src="icons/arrow.svg" alt="Big arrow" />;

Even if this icon can be manipulated exactly as an image, advantages of SVG, except file size reduction, cannot be used. In particular, customization is not possible. Moreover, if the file is moved or deleted, developers may not be aware of it. Let's see what's next!

SVG as a background image

A really simple way to create an icon is to set it as the background image of a div element.

import styled from "styled-components";

const Icon = styled.div`
  ${({ url }) => `background-image: url(${url});`}
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background-size: cover;
  background-position: center center;

  ${({ size }) => `
    width: ${size}px;
    height: ${size}px;
  `};
`;

const ArrowIcon = <Icon url="icons/arrow.svg" size={20} />;

The icon can be manipulated as a div element, so the size is customizable, but that's about it. Thus, let's go through another way.

SVG as font

One popular method to manage a big set of icons is to use a font. It is quite easy to generate a font with online tools such as IcoMooon or Fontello.

A generic icon component should look like:

import "./style.css";

const Icon = (props) => (
  <span>
    <i className={`icon-${props.slug}`} />
  </span>
);

Where the file style.css, normally created by a font generator, should be something like:

@font-face {
  font-family: "icons";
  src: url("fonts/icons.woff2") format("woff2"), url("fonts/icons.woff") format("woff");
  font-weight: normal;
  font-style: normal;
  font-display: block;
}

.icon-arrow:before {
  content: "\\e954"; /* `\\e954` is a character defined in the font */
}

For additional props, you can wrap the icon, replacing span by a styled component with props. For instance:

const WrappedIcon = styled.span`
  ${(props) => `
    i {
      ${props.color ? `color: ${props.color};` : ""}
      ${props.size ? `font-size: ${props.size}px;` : ""}
    }
  `};
`;

And finally get a simple icon component:

const ArrowIcon = <Icon slug="arrow" size={20} color="blue" />;

This method seems to be used in a lot of projects. Before challenging it, let's see the last one.

Inline SVG

Importing icons in React (granted it is properly configured, we'll see that later) is as simple as that:

import Arrow from "icons/arrow.svg";

const MyComponent = () => <Arrow />;

This is the method I chose in my case, let's understand why.

Comparison

Criteria
✅: OK
⚠️: Points of attention
⛔: KO if it is a regarded criterion
Inline SVG Font SVG as image SVG as background image
Customization ✅ Highly customizable: size, color, etc. and specific properties fill and stroke ⚠️ Customizable like text only: size, color, etc. ⛔ Limited customization, same as image: size, etc. ⛔ Limited customization, via its div container: size, etc.
✅ All parts of the SVG can be customized finely ⚠️ Painful positioning as it uses text positioning CSS
Performance ✅ Smaller files compared to images ✅ Smaller files compared to images ✅ Smaller files compared to images ✅ Smaller files compared to images
⚠️ Icons are loaded with the DOM: it may slow the page loading, but ensures all icons are loaded at the same time as the page ✅ Can be loaded thanks to a CDN ⚠️ Icons are loaded with the DOM: it may slow the page loading, but ensures all icons are loaded at the same time as the page ⚠️ Icons are loaded with the DOM: it may slow the page loading, but ensures all icons are loaded at the same time as the page
⚠️ Font is loaded in parallel: blank characters can appear. All icons are loaded even if few are used
Maintainability ✅ Easy to add icons progressively ⚠️ Painful to regenerate a font, even if some external tools like Icomoon or Fontello can help ⛔ Difficult to assert images path are valid ⛔ Difficult to assert images path are valid
Developer Experience ✅ Good experience if SVG files are easy to customize ✅ Good experience once the font is configured ✅ Good experience ✅ Good experience
Accessibility ✅ Possibility to define title and desc tags ⛔ Not accessible without large efforts ✅ Accessible like images ⛔ Not accessible

Let's dive further into the comparison of the two concurrent methods: inline SVG vs. font...

Customization

Fonts are managed like text and it is therefore possible to customize an icon's size or color. Precise positioning is possible with the same properties as text (font-size, line-height, text-align, etc.), so it is quite painful when icons are used as buttons or images. For emojis used in a text, it could be more relevant, however.

SVG are customizable as images and properties fill and stroke allow to redefine colors respectively of the content or the border. All parts of the SVG can be separately customized, which is impossible to do with fonts.

➡️ Using inline SVG is the best choice if you need color variants of icons, or to customize precise parts of them.

➡️ Icons used as characters should be part of a font.

Performance

Fonts are loaded asynchronously, which means blank characters can appear while the font is being loaded.

All icons are loaded in the font, even if only a few of them are used on the page. However, it can be served by a CDN, a fast method if CORS is correctly configured.

On the contrary, SVG are loaded with the DOM, which avoids blank spaces, but relatively increases loading duration. An icon usually weighs from 500B to 5kB (less with SVG Optimization) so it can be non-negligible if hundreds of icons are used at a time - quite rare, however!

Fonts are also subject to anti-aliasing methods applied by browsers to font characters when scaling. SVG do not suffer from this issue and are fully scalable.

➡️ In pages with hundreds of icons, using a font reduces the page size. Serving it via a CDN is a performant option.

➡️ For pages with moderate use of icons, SVG is a better option to avoid anti-aliasing and blank characters at loading.

Maintainability

What if icons evolve along the development process?

With a font, you need to regenerate the font correctly, with the correct source files - otherwise, some character codes may change!

With SVG, you just need to add, replace or delete a file and update file imports in the project. In case of replacement, check the SVG structure is still the same, as fine customization of SVG parts may be affected.

➡️ If icons are likely to evolve, SVG files are a more consistent and independent format to handle icon addition, edition, or deletion.

Developer experience

Once icon systems are set, both methods are easy to use as developers, as seen in the first part.

The only point of attention can be when it is necessary to customize SVG icons that are already styled. For instance, with a fill or a width property already set:

<svg width="15px">
  <path fill="none" d="..." />
</svg>

In this case, applying custom fill or width won't be applied. Cleaning the SVG by removing fill="none" and width="15px" should fix the issue.

➡️ Once an icon system is set, the developer experience should be satisfying in both cases.

Browser compatibility

Fonts are supported by almost all browsers, using the @font-face rule. All modern web browsers support WOFF2 and WOFF which are optimized formats for the web, but other formats can be used for broader browser support. However, Opera Mini does not support @font-face at all.

All modern browsers now support inline SVG method, even Internet Explorer which only does not support CSS transforms (more details on caniuse website).

➡️ Both methods are supported by more than 98% of web browsers. Inline SVG is slightly better supported and has better fallback options.

Accessibility

Serving accessible fonts is hard. If you are adventurous, this article details the tricks to guarantee accessible fonts, so I won't expand on it here.

If accessibility is important for your project, then SVG are made for this. An SVG file may have a title or even a desc (for description) tags. For better browser support, it is possible to add an id to these tags and refer to them the attributesaria-labelledby as the following minimal example shows.

<svg aria-labelledby="circleTitle circleDescription" role="img">
  <title id="circleTitle">A circle</title>
  <desc id="circleDescription">A red circle with a blue border</desc>
  <circle cx="50" cy="50" r="40" stroke="blue" stroke-width="2" fill="red" />
</svg>

For more details, I advise you to look at this CSS-tricks page.

➡️ For accessible projects, SVG is the best option with no hesitation.

A winner?

A font system is an option to consider if your project uses a huge amount of icons per page, is focused on performance, does not need to be accessible, and has a graphic chart that won't evolve.

In other cases, for better flexibility and user experience, I would recommend an inline SVG system. That's what I chose in the case of a new B2C project with common use of icons (<30 per page) and a library with possible evolutions.

In the following part, I will explain how to configure such an icon system.

Configuring an SVG icon system with Next.js, React and styled-components

Here are the four simple steps I followed to integrate icons in a Next.js/React project using styled-components as styling library.

  1. Install babel-plugin-inline-react-svg:
# With npm
npm install --save-dev babel-plugin-inline-react-svg
# or, with yarn
yarn add --dev babel-plugin-inline-react-svg

This package will allow the import of SVG files as React components. It also uses SVGO to optimize SVGs.

Note: SVGR also offers good alternatives to babel-plugin-inline-react-svg depending on your bundler, such as @svgr/webpack.

  1. Add inline-react-svg plugin to babel configuration (babel.config.js or .babelrc):
plugins: ["inline-react-svg"];

For further configuration, refer to the npm page.

In a TypeScript project, you should also add a module declaration in a type file:

// fileTypes.d.ts

declare module "*.svg" {
  import React = require("react");
  const src: (props: React.SVGProps<SVGSVGElement>) => JSX.Element;
  export default src;
}
  1. Now, it is possible to import directly SVG as React components. For a resilient icon system, it is possible to create a JSX/TSX file per icon:
// icons/Arrow.jsx
import Arrow from "./arrow.svg";

// ... (possible customization of the component)

export default Arrow;

With this method, refactoring icons is easier if the import method changes.

Warning: using a unique index file with all SVG icons gathered in it is not recommended, because it prevents from tree-shaking the library.

  1. Import SVG files as React components:
import Arrow from "icons/Arrow";

const MyComponent = () => <Arrow />;

It is also possible to customize icons with styled-components, as shown in this example:

// MyComponent.tsx
import styled from "styled-components";
import Arrow from "icons/Arrow";

const CustomSpan = styled.span`
  color: red;
  font-size: 40px;
`;

const CustomArrow = styled(Arrow)`
  width: auto;
  height: 30px;
  stroke: blue;
  stroke-width: 50px;
  fill: currentColor; /* fill with the current color, here red */
`;

const MyComponent = () => (
  <button>
    <CustomSpan>
      Go <CustomArrow />
    </CustomSpan>
  </button>
);

...which renders as a wonderful button:


That's all you need to manage icons in your project!

Guillaume Égée

Guillaume Égée

Theodo - Developer