Flexible SVG components in React
TL;DR: Using SVGs inside of React components allows us to control their size and color and SVGR is the right tool to create and maintain many SVG components at once.
While getting started with a new project, a colleague asked me about a good way to include icons into a next.js based React application.
It’s been quite a while since I implemented an icon system in one of my previous projects, so I quickly opened the source files of that project to refresh my memories. In hindsight, I felt that my approach worked quite well for what we were doing and I decided to share my insights in this blog post. Let’s dive in!
Getting SVGs into our projects
Using SVGs on the web isn’t hard, the format is supported by all major browsers since IE9. Of course, there are multiple ways to include them into our projects, like using them in image tags, as background-images or directly inline.
While all approaches are easy, the latter offers the opportunity to create self-contained, on-the-fly-customizable icon components, so I wanted to make use of that.
Suppose we have an icon SVG depicting an airplane, that we want to use in various colors and sizes throughout our project. The SVG code might look something cryptic like this:
<svg
version=”1"
xmlns=”http://www.w3.org/2000/svg"
viewBox=”0 0 50 50"
>
<path
fill=”#FFF”
d=”M33.5 45.5c-.1 0-.2 0-.3-.1L25 41.3l-8.2 4.2c-.1 0-.2.1-.3.1-.1 0-.2 0-.3-.1-.2-.1-.3-.3-.3-.5v-5.3c0-.2.1-.3.2-.4l4.1–4.4v-4.2L5 35.1h-.2c-.1 0-.3 0-.4-.1-.2-.1-.3-.3-.3-.5v-5.2c0-.2.1-.4.3-.5L20 17.7V9.5c0–3.1 2.2–5.6 4.9–5.6s4.9 2.5 4.9 5.6v8.1l15.6 11.1c.2.1.3.3.3.5v5.2c0 .2-.1.4-.3.5-.1.1-.2.1-.4.1h-.2l-15.1–4.5v4.2l4.1 4.4c.1.1.2.3.2.4v5.3c0 .2-.1.4-.3.5 0 .2-.1.2-.2.2zM25 38c.1 0 .2 0 .3.1l5.9 3v-.6l-4.1–4.4c-.1-.1-.2-.3-.2-.4v-8.3c0-.2.1-.4.3-.5.1-.1.2-.1.4-.1h.2l15.1 4.5v-.9L27.2 19.3c-.2-.1-.3-.3-.3-.5V9.5c0–1.4-.9–2.6–1.9–2.6s-1.9 1.2–1.9 2.6v9.3c0 .2-.1.4-.3.5L7.2 30.4v.9l15.1–4.5h.2c.1 0 .3 0 .4.1.2.1.3.3.3.5v8.3c0 .2-.1.3-.2.4l-4.1 4.4v.6l5.9–3s.1-.1.2-.1z”
/>
</svg>
So far so good, your typical .svg file. What’s of notice is that there are properties for the viewbox and the path’s fill. While the viewbox specifies the area that contains the path, there are more possible properties on the root svg
tag, specifically width
and height
as well as fill
.
The fill
property, for example, can be specified as
<svg ... fill="#f00">
and reused inside of the path
tag (or tags) for the specific paths like so.
<svg ... fill="#f00">
<path
fill=”fill”
d=”M33.5 ...”
/>
</svg>//the d property was shortened for readability purposes
Lord of the Props
This is a perfect opportunity for controlling these properties via props
thrown into a React component that renders the svg
tag. We can now easily define our Airport
stateless functional component as
const Airport = props => (
<svg version={1} viewBox="0 0 50 50" {...props}>
<path
fill="fill"
d="M33.5 ..."
/>
</svg>
);//the d property was shortened for readability purposes
and use it via:
<Airport width={20} height={20} fill='#fff' />
It’s over, it’s done! We got ourselves a nice, controllable little component, where we can specify height
, width
and fill
to cover our various use cases. We are now true rulers of the SVG world, aren’t we? But what if we had to manage hundreds of icons in a large-scale application? Wouldn’t exporting, optimizing and setting up components take hours — and what if icons change? Oh, the mess!
SVGR to the rescue
There surely must be a package that handles all this — and SVGR is the one. After installing the dependency via npm install svgr
in our project root, we can easily create React components off our many .svg files automatically.
Let’s add a new npm script to our package.json
file and call it icons
:
"icons": "svgr --replace-attr-value '#FFF=fill' -d ./src/icons/js ./src/icons/svg"
Basically, this command takes the path ./src/icons/svg
as the location to our /svg
directory containing all of our icons and outputs the icons as components into the ./src/icons/js
directory. The --replace-attr-value
is the magical part of the script, where we replace thefill
attribute value of"#FFF”
with "fill"
as explained above.
After running this command via npm run icons
, all our components are created inside of the /js
directory. Suppose we had our airport.svg
file inside of /svg
, we now have the corresponding Airport.js
file containing the code above — by merely lifting a finger. Awesome!
Taking this further
We might want to access the icons by a String value, or we might want to color multiple path
inside our components differently, which is all possible. To keep this post concise, I will cover these aspects in the future. Stay tuned!