SVG Icon Systems
If youâre doing this in your React project⌠just stop it! đ
// some-file.js
import iconA from './icons/icon-a.svg';
import iconB from './icons/icon-b.svg';
import iconC from './icons/icon-c.svg';
import iconD from './icons/icon-d.svg';
// some-other-file.js
import iconA from './icons/icon-a.svg';
import iconB from './icons/icon-b.svg';
import iconC from './icons/icon-c.svg';
import iconD from './icons/icon-d.svg';
But why is that bad?
As youâre probably aware âdesign systemsâ are a current trend and more or less they attempt to reduce the amount of âone offâ styles in any code base in favour of a more general re-usable set of styles that work in a number of places around the code base. This approach makes the code base easier to maintain, usually results in fewer bugs and if a design system is created and used correctly it can dramatically speed up development time and cut down on development costs.
⌠and my thoughts are, why should SVG icons be any different?
Iâve explained this concept numerous times and always end up hunting around an old repo for an <SvgIcon />
component
and notes to explain the principles to various members of a team.
Iâm primarily writing this post so i donât have to explain this again. đ
Letâs start with a simple principle.
The âSâ in Svg stands for scalable. This means it doesnât matter what size the icons are designed at because we can use CSS and or HTML attributes to make them bigger or smaller without losing quality.
Now thatâs out of the way lets pick up on that point i just madeâŚ
it doesnât matter what size the icons are designed at
Whilst this is true it does matter if some icons are designed at 24x24 and some are designed at for example 48x48.
Weâre going to build an icon system and one of the requirements of our system is that ALL the icons are designed using the same specification.
The specification is as follows and could be used as Acceptance Criteria if youâre looking to write a ticket for your design team.
- It must be designed on an artboard that is 24x24(px)
- It must have one single transparent path that is the full 24x24(px)
- It must have one single designed icon path
- It must be black
That last point doesnât actually matter but if you design the icon in white and put it on a white artboard it makes it harder to see in your OS preview and when you open the file up in Illustrator.
To explain a bit more iâm going to use an icon from
material icons. This is settings-24px.svg
and on the left is
what it looks like in Illustrator and on the right is what it looks like in VS Code.
In Illustrator youâll notice thereâs two paths nested in a layer. The first path is the settings icon, the second path is a 24x24 transparent path.
Weâre mostly interested in the settings icon, notice how it doesnât touch the edges of the artboard, this is fine! not all icons need to be the same and fill the artboard⌠wait, what.. isnât that a âone offâ rule that breaks the system?
Nope! â
The reason is because ALL icons will have a 24x24 transparent rectangle path meaning they are all the same!
PHEW! The system remains in-tact.
If we look at VS Code youâll notice theres also two paths, one for the settings icon and one for the 24x24 transparent
rectangle, you can also see that the HTML attributes for width
and height
and viewbox
values for width
and
height
are all 24
. This is what weâre after, everything in the SVG has been designed to be consistent and to a fixed
value of 24
If you download any of the icons in the material icons set youâll see the same set of rules.
If you can get your designers to follow this consistently you will have all the things you need for a solid and re-usable SVG icon System.
Weâre now going to create a utils file to store our icon âpathsâ and build an SvgIcon
component in React
// icon-paths.js
export const SETTINGS =
'M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z';
export const PERSON =
'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z';
export const FOLDER = 'M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z';
To extract the paths from an SVG open it up in VS Code and copy the string. Using material icons itâs easy to identify which path is the icon and which is the 24x24 transparent rectangle as itâs usually the longer one. To help you identify which path is which, the 24x24 transparent rectangle looks like this đ
<path d='M0 0h24v24H0z' fill='none' />
Now lets create a component that has props for iconName
, size
and color
. We are using the second path with
fill="none"
just in the component and this is the 24x24 transparent rectangle as mentioned earlier.
// SvgIcon.js
import React from 'react';
import PropTypes from 'prop-types';
const SvgIcon = ({ iconName, size, color }) => {
return (
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height='100%'
color={color}
fill='currentcolor'
viewBox='0 0 24 24'
preserveAspectRatio='xMidYMid meet'
x='0'
y='0'
>
<path d={iconName} fill='currentcolor' />
<path d='M0 0h24v24H0z' fill='none' />
</svg>
);
};
SvgIcon.defaultProps = {
size: 24,
color: 'inherit',
};
SvgIcon.propTypes = {
/** The name of const in icon-paths.js */
iconName: PropTypes.string.isRequired,
/** The size to act as the width of the icon */
size: PropTypes.number,
/** The fill color of the path */
color: PropTypes.string,
};
export default SvgIcon;
and your usage would look something like this đ
// SomeComponent.js
import { SETTINGS, PERSON, FOLDER } from "./icon-paths"
import SvgIcon from './SvgIcon'
...
<SvgIcon iconName={SETTINGS}/>
<SvgIcon iconName={PERSON} size={60}/>
<SvgIcon iconName={FOLDER} size={120} color='#0000ff'/>
...
Naturally you can add more props as per your requirements and you can change the default size and color in default props
to suit your needs but, using this method you only ever have ONE actual SVG in your code base. You can of course
pass it any path from icon-paths.js
and control itâs size and color via props.
Iâve used this method a lot and itâs never let me down.
The only watch outs are getting the design team on board. If you go down this road and then later icons are designed with different viewbox sizes or on different sized artboards you canât just make a âone offâ change to fix just one icon, that said if the SVGâs themselves follow the rules of our system you shouldnât have any issues.