Design System Sprint 6: The Fastest Icons on Earth

A brief introduction: I’m Marcin: a former UX manager and now co-founder and CEO at UXPin — the full stack UX design platform. In this series of posts, I’m reporting on UXPin’s progress in creating our own design system. Every sprint of UXPin’s Design Operations team is summed up as a post in the series. We started by discussing the reasons to build a design system and subsequently I shared stories of:

Today, we’re going to talk about the management of icons in a design system. If you’re wondering how to implement an icon library in your design system and projects to get the maximum performance, fastest development and most flexible styling — this post is for you! Embark on the journey to the fastest icons on Earth!

Icons — The Meaning

Can you imagine modern design without iconography? Signage systems, operating systems, desktop software, web, mobile and all sorts of physical products — whenever the speed of recognition of a desired interaction is crucial, icons offer a mental shortcut.

Without icons, interfaces, would most likely, create higher cognitive load by forcing users to either closely consider every action or try all available options. Playing any music or videos without a familiar “play” icon would be difficult. Finding a bathroom at the airport without familiar ideograms would turn into an unbearable experience.

Modern design without iconography is unimaginable.

Fortunately nobody has to imagine the world of design without icons. Icons are more available than ever with plenty of great icon families at our fingertips (the noun project, font awesome, uxpin free icons… ) and lots of others available to download and purchase (check iconfinder.com).

The visual maturity of icon systems also reached new peaks. Just take a look at any complete modern icon systems, such as icons in Material Design. High visual coherence and clarity of messaging are expressed across the entire set of icons. Stroke, radius and the overall style are well balanced and ideas represented by most ideograms are easy to grasp.

Modern icons systems are deliberate parts of the visual communication language, not accidental fillers of space.
Coherent system of icons from Google Material Design

Icons, as a universal and key part of a visual language, don’t go through revolutions of their form. Line and hairline icons (icons with thin strokes and no fill) might be more popular these days than ever before, but they’re not fundamentally different than their predecessors.

Icons go through constant evolution, rather than revolution.

Line style Icons from a free UXPin Icon Library look modern but are somehow similar to the icons in the legendary GUI of Xerox Star (1981)
Icon in Xerox Star GUI. Source. License CC BY-SA 3.9

The technical side of icons, on the other hand, changed tremendously over the years. Long gone are the days when icons were simple images embedded in the markup.

In several waves, the technical side of icons have been thoroughly optimized to provide maximum performance, flexibility, browser compatibility, design/development friendliness, and accessibility. Our industry fell in love with image sprites, icon fonts, base64 encoding, inline SVGs- always chasing the new, better, implementation.

Which of these techniques is the best? And what does “best” means in this context? To make the right choice for a design system, we have to answer these two questions.

Icons — The Requirements

Before we’ll jump into the technical implementation of icons, check if you have icons worth placing in your design system.

Your icons should be:

  • Visually coherent — set on the grid, with identical terminals and stroke and with identical overall style (line work, flat etc.)
  • Legible at all desired sizes — users should be able to comfortably see icon at any size defined in the interface
  • Understandable — users should be able to easily recognize what action or concept is represented by an icon

If your icons pass these three criteria, you probably have at least a decent Icon library. That’s a good start! If you need more guidelines, I highly recommend the story of Etsy’s Icon System written by Marco Suarez.

So do UXPin icons pass these criteria? Yes, we believe so and our belief was confirmed in tests with users. The current set of icons has served us since 2015 and survived significant changes in the user interface. We continue to use it because…. if something is not broken, why fix it? The success of iconography can be seen in the feeling of familiarity felt by users. Icons should be a universal signage system that stays consistent throughout UI tweaks and even full redesigns.

Icons have to trigger a feeling of familiarity and safety in users. They should last longer than the rest of the interface.
Icon library used in UXPin UI

Despite having a well established icon library, we had to think through the implementation.

For years now we’ve been using icon fonts — a method that renders icons saved in font files on, either infrequently used unicode code points ( ‘locations’ of glyphs in a font file), or so called private use areas (PUA).

Icon fonts are great at providing flexibility in implementation (color and size can be easily changed through CSS) and look great in all the modern browser (warning: icon fonts won’t work in any browser that does not support @font-face — that means that no version of Opera Mini will be able to render it) and are fairly developer friendly. Although the straight implementation might be considered somehow quirky, with icons defined in CSS pseudo-elements through a unicode code point saved as “content”, the maintenance is a breeze with any preprocessor and icon declarations saved in mixins.

Icon fonts are also typically praised for their performance, but as we’ll see in couple of minutes, our tests showed that when the number of rendered icons is high, only a few options are slower than an icon font.

Icons rendered from font files come also with some accessibility issues, which can be worked around, but still they require extra effort. You can read all about these issues in the excellent article: Bulletproof Accessible Icon Font.

Summing up benefits and shortcomings of Icon Fonts encouraged us to:

  1. Define how a perfect implementation of icons in UXPin would look like;
  2. Benchmark popular methods to find a method that truly serves our needs and can provide a solid platform for the future growth.

Here’s our evaluation criteria for icon implementation:

  1. Performance — Some simple, intuitive, methods will slow the application and the overall user experience. For example, using icons as separate image files will result in an unnecessarily long load of your application due to a high number of http requests. This slowness is easy to avoid with multiple other methods.
  2. Flexibility of styling — A good design system has to balance flexibility of options with the rigidness of rules. Icons have to keep a consistent form, but at the same time should have easily customizable size and color within the boundaries defined by the system. In theory, you can prepare png sprites in every size and color variation, but any subsequent update of this library will be a nightmare and sooner or later somebody will make an error that will go straight to production servers.
  3. Developer-friendliness — Many interfaces use dozens of icons in multiple locations. If the implementation of icons is not easy and fast to use, maintaining and updating the UI is a nightmare. Dozens of icons have to be also always kept up to date so that developers aren’t stopped in the middle of the their process and forced to wait for the newest assets.
  4. Designer-friendliness — Designers are curators and guardians of icons libraries. As fun as it sounds, it also involves generating new version of assets in all the necessary formats. The less the better.
  5. Browser compatibility — Although most of the techniques used to manage icons have a broad support among browsers, some are still troublesome. Font-face (necessary to make use of icon fonts) is not supported by Opera Mini, data URLs are only partially covered by Microsoft IE and Edge. If you have to support these browsers, you need to choose methods that are going to work or figure out fallbacks.
  6. Accessibility — As a designer or developer, you want your product to be available to the entire target group. Some of the popular icon implementations (as mentioned earlier, icon fonts) come with certain accessibility issues which have to be actively managed.

Taking into account these criteria let us define a perfect implementation:

Perfect implementation of icons in a design system provides maximum performance for all the users, allows for dynamic styling, and lets designers and developers focus on their craft rather than maintenance of artificial technical assets.

With this concept in mind we’ve moved to the research.

Icons — The Research

The only way to know is to test.

Instead of guessing which method fits our requirements, I decided to try them all, thoroughly measure performance, and take plenty of notes.

Some of the methods of implementing icons could have been ruled out as inefficient just based on the theory. For example, it’s quite obvious that directly linking 45 png files with icons is going to be a performance killer due to a number of http requests. Curiosity took over and I’ve decided to test them as well, to learn how bad (or slow) it can really get (spoiler — it gets really slow).

The tested methods were:

  1. React.js Components rendering an inline SVG Icon — a simple React component that takes an imported svg file (turned into inline svg through Inline SVG Loader) through props and renders it as an inline SVG with defined properties (in tested case — size and color, but could be anything).
  2. SVG icons loaded inline as data URLs — inline SVGs are saved as strings in sass variables and used as background in a data URL (without base64 encoding). To provide maximum flexibility, I’m adding color variables to ‘fill’ in the svg string. Size is being defined through background-size.
  3. PNG Sprite — all the icons are placed in one image called sprite and referred to in CSS through the position of the background. Process is automated thanks to IcoMoon.
  4. SVG Sprite — exactly the same process as used in the PNG Sprite version, but with sprite saved as an SVG file.
  5. PNG icons loaded individually as data URLs encoded with Base64 — webpack url-loader turns all the imported PNG files into strings encoded with Base64 and adds them to CSS data URLs.
  6. SVG icons loaded individually as data URLs encoded with Base64 — webpack url-loader turns all the imported SVG files into strings encoded with Base64 and adds them to CSS data URLs.
  7. Icon Font — icon font generated with IcoMoon and added to CSS with @font-face. Individual icons are saved as classes with :before pseudo-element.
  8. Individual PNG Icons — individual PNG files linked as src parameter of an img element
  9. Individual SVG Icons — individual SVG files linked as src parameter of an img element

Methodology

The methodology was very simple.

Every method has been tested with a full implementation of a set of 45 icons and a partial implementation with selected 3 icons. In the latter case, if the method used one file to implement 45 icons (sprites, icon fonts…) the same file had been used to render 3 selected icons.

To perform the test, I’ve built a simple React application that generates a list of icons. For every method the base component looked, as far as it was possible, identical (so the impact of the differences in JS code could be minimal). Here’s an example of the code used to generate list for the method using React.js component rendering inline SVG icon (if you’re interested in the entire code, you can see it here) :

import React, { Component } from 'react';
import Icon from './IconComponent';
import IconAddMember from '../../assets/icons/SVG/icon-add-member.svg';
import IconAddNote from '../../assets/icons/SVG/icon-add-note.svg';
import IconAddSth from '../../assets/icons/SVG/icon-add-sth-sb.svg';
....
const Icons = [IconAddMember, IconAddNote, IconAddSth];
export default class SvgIconsComponent extends Component {
   render(){
return(
<ul className='b-icons-list'>
{Icons.map((item, i) => {
return(
<li key={i}>
<Icon svg={item}
width="30"
height="30"
color='#555'/>
</li>
)
})}
</ul>
)
}
}

First, the right icons get imported. Then, based on an array of variables, the list of icons gets generated. This process was used to test every single one of the nine methods.

Rendered list of icons used for testing

I deployed this simple app to Heroku and for every method the total load time was measured at 10 times per condition (45 icons, 3 icons) with cache turned off.

I recorded all the results in a spreadsheet:

And… that’s it. Nothing extremely complicated, but it does the job.

Results

The results were quite surprising. While naturally individual PNG and SVG files were performing terribly, I was really surprised by how fast a React.js component was at rendering an inline SVG, how decent a SVG loaded as a data url in CSS and…how slow the icon font was.

Results of the test of 9 different methods of managing icon libraries

React.js components proved to be the fastest method (or at least as fast as base64 encoded PNGs), while providing the most flexible styling and the most developer/designer friendly environment and superb accessibility (thanks to title and desc tags — read this article for details).

If the number of icons is high, React.js component rendering an inline SVG can be over 40% faster than an icon font. The number decreases when the number of icons is smaller, as it seems that the biggest influence on rendering is the custom font, rather than the size of the icon file.

Other Criteria

Our little benchmark showed that React.js component rendering an inline SVG is the fastest method of implementing icons, but base64 PNGs and SVGs, as well as sprites come quite close (since differences are so small, these methods are likely equally fast).

What about the other criteria? Are there also equal? No. I’d argue that some of these methods, though performing well, fail at being reliable for icon management in a mature design system.

The case against Base64 encoded PNGs and SVGs

Let’s start with an extremely quick method of implementing icons and any other images — encoding them with base64. If you’re unfamiliar with this method, here’s a great article: When to Base64 Encode Images (and When Not To).

Base64 definitely improves performance, but at the same time decreases styling flexibility (you can’t use e.g. sass variables in encoded SVGs). While size can be modified through background-position, you’ll have to define different colors with different files.

That’s definitely not a developer nor designer friendly solution. Accessibility can be worked around through assigning the background to a markup element with a defined alt attribute.

Encoding images with base64 is definitely a good idea for performance reasons, but it seems that there are better methods of managing icons.

The case against Sprites

Sprites perform quite well, thanks to a reduction in the number of HTTP requests.

PNG sprites are somehow faster (due to a smaller file size than SVG sprites), but don’t provide any flexibility in styling. SVG sprites are a little bit better — the size can be modified, but unfortunately modification of colors can only be hacked through css filters, which is less than ideal and does not stay in sync with the color palette.

Sprites used to be a pain to be managed by designers and developers (after all it’s an artifact only used for development and somebody needs to remember to keep it up to date), but with task managers we got tools to automatically built and use sprites from a repository of assets (here’s a great article discussing generating SVG sprites with Gulp).

Nevertheless the lack of ability to change colors of icons in a sprite kills the whole idea of using it in a design system. The lack of connection between color palette defined in a system and implementation of icons, sooner or later, will be a problem for the team.

The case against Icon Fonts

Icon Fonts are very flexible and easy to style. Once you have a good process, they are OK for developers and acceptable to designers (though somebody needs to remember to generate a new font file when an icon is added or removed from the icon system). You can even manage accessibility (frequently mentioned as a downside of icon fonts).

Performance however, leaves much to be desired. If, like us, you’ve been using icon fonts for years, there’s a lot of refactoring needed to get rid of this method. But if performance is a priority, you should not be tempted to compromise.

There are better methods.

The case against SVG icons loaded inline as data URLs

This method is somehow nonstandard and perhaps require a little bit more explanation. The whole idea is that inline SVGs can be added as CSS background through a data URL. To achieve it, I created a SCSS file with SVG strings assigned to Sass variables. For example:

$icon-add-member-string: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="20" viewBox="0 0 16 20" fill="#{$icon-color}"><title>icon-add-member</title><path d="M5.714 10c0.857 0 1.429 0.571 1.429 1.429s-0.571 1.429-1.429 1.429-1.429-0.571-1.429-1.429 0.571-1.429 1.429-1.429zM5.714 8.571c-1.571 0-2.857 1.286-2.857 2.857s1.286 2.857 2.857 2.857 2.857-1.286 2.857-2.857-1.286-2.857-2.857-2.857v0z"></path><path d="M5.714 15.857c2.286 0 4.143 1.857 4.143 4.143h1.571c0-3.143-2.571-5.714-5.714-5.714s-5.714 2.571-5.714 5.714h1.571c0-2.286 1.857-4.143 4.143-4.143z"></path><path d="M15.714 2.857h-2.857v-2.857h-1.429v2.857h-2.857v1.429h2.857v2.857h1.429v-2.857h2.857z"></path></svg>';

Subsequently I wrote a simple Sass function to clean up every SVG string and make it ready to be set up as a background. Whitespace between tags had to be removed and unacceptable HTML characters had to be escaped:

@import 'inline-svg-strings';
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-
replace(str-slice($string, $index + str-length($search)),
$search, $replace);
}
@return $string;
}
@function svg-cleaner($string) {
$result: str-replace($string, '"', '\\\'');
$result: str-replace($result, '\\r', '');
$result: str-replace($result, '\\n', '');
$result: str-replace($result, '\\t', '');
$result: str-replace($result, '\\v', '');
$result: str-replace($result, '\\f', '');
$result: str-replace($result, '\\b', '');
@return "data:image/svg+xml;utf8," + $result;
}

The string clean up function was inspired by the work of Hugo Giraudel and Terabaud.

Every SVG String also gets a Sass color variable defined as ‘fill’, so the color of an icon can be controlled just as any color of an object in the design system.

Finally I’m assigning clean strings to Sass variables and using them in simple mixins included into classes:

$icon-add-member: svg-cleaner($icon-add-member-string);
.b-icon__add-member {
@include icon($icon-add-member, 20px, center);
}

The set-up is somehow complicated, but the usage is quite easy with mixins and sets of variables.

The process of building SVG inline strings and cleaning them can be moved to javascript and become part of the build process to further optimize this process for developer/designer friendliness.

So what’s the problem with these method? It’s nearly perfect, but it’s not as easy to set-up and not as fast as the React.js Icon component, so why should we bother? I’m having a hard time finding an answer to this question.

Recommendation

By now you know what my recommendation to the team is going to be: let’s move icons to React.js components. This is the fastest method, most designer/developer friendly (one shared repository of assets!), most accessible (SVGs with titles and descriptions), and definitely most flexible when it comes to styling. Not only you can easily manipulate size and color. You can even animate SVG paths if you want to! Implementing icons through React.js components is by far the most future facing method.

What if your company does not use React.js? What if the team wants to keep icons in CSS? In that case inline SVGs in data URLs are the second best option. You’ll still get good performance and decent flexibility of styling, without the limitations of Sprites or Base64 encoded images.

Naturally implementing any of these two methods is going to require significant refactoring. At the time of writing, we’re still discussing the ideal way forward.

Special thanks for guiding me towards React.js component solution to: Jon Gold (Airbnb), Dominic McPhee and Chris Sauve (Shopify), @Jennifer Stern (Instructure UX), Carl Nelson (Clever Inc.), @Matt Vagni (Lyst), Tim von Oldenburg and Design Systems Slack Community!