Embedding SVG Icons in CSS
I cannot quite recall what gave me the idea to embed all SVG icons in CSS instead of importing them as JSX components. Perhaps I was struggling to manage the SVG assets of several small projects at the time. Either way, this post explores how to embed SVG icons in CSS.
Probably Don’t Base64 SVG makes the case for keeping SVG data URIs as text instead of Base64-encoding them. Normally, a URL-encoded SVG goes in background-image: url(URL_ENCODED_SVG);. However, this makes it hard to change the color of the SVG. CSS masks let us turn the SVG into a mask and change the element’s background color, which then sets the color of the icon.
To show how this works, take the Entypo home icon as an example. Here is its SVG source code:
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Home" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
<path d="M18.672,11H17v6c0,0.445-0.194,1-1,1h-4v-6H8v6H4c-0.806,0-1-0.555-1-1v-6H1.328c-0.598,0-0.47-0.324-0.06-0.748L9.292,2.22
C9.487,2.018,9.743,1.918,10,1.908c0.257,0.01,0.513,0.109,0.708,0.312l8.023,8.031C19.142,10.676,19.27,11,18.672,11z"/>
</svg>We can URL-encode this icon with URL-encoder for SVG:
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Home' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 20 20' enable-background='new 0 0 20 20' xml:space='preserve'%3E%3Cpath d='M18.672,11H17v6c0,0.445-0.194,1-1,1h-4v-6H8v6H4c-0.806,0-1-0.555-1-1v-6H1.328c-0.598,0-0.47-0.324-0.06-0.748L9.292,2.22 C9.487,2.018,9.743,1.918,10,1.908c0.257,0.01,0.513,0.109,0.708,0.312l8.023,8.031C19.142,10.676,19.27,11,18.672,11z'/%3E%3C/svg%3E");Now, all we need to do is replace the background-image property with mask-image and add background: currentcolor;, so that the icon is painted in the element’s color, which comes from the parent unless the element sets its own. An icon then changes color with the text around it.
background: currentcolor;
mask-image: url("URL-encoded SVG");A mask paints only inside the element’s box, so the element needs a width and a height. An inline element such as <span> ignores both until it gets display: inline-block, block or similar.
As with background images, mask-repeat: no-repeat; and mask-position: center; center the icon and stop it from repeating as a tile.
.icon-home {
display: block;
width: 24px;
height: 24px;
background: currentcolor;
mask-repeat: no-repeat;
mask-position: center;
mask-image: url("URL-encoded SVG");
}The code above is a basic working example. If we replace background: currentcolor; with something like background: linear-gradient(...);, the icon becomes a gradient, which can also be animated:
Creating SVG icons this way one by one can be tedious, so I built a project called SVG-in-CSS to help manage your SVGs and generate the necessary CSS code. It was inspired by both URL-encoder for SVG and IcoMoon. You can also manage different projects: download the svg-in-css.config.json file and upload it later to restore your previous project settings. The source code is on GitHub.
Optimizing SVG with SVGO
Since I use SVGO to optimize all SVG assets, the SVG-in-CSS project includes a browser version of SVGO with the following configuration:
{
plugins: [
"preset-default",
"removeDimensions",
{
name: "prefixIds",
params: {
prefix: `svg-${FILE_NAME}`
}
}
]
}The removeDimensions plugin removes the width and height attributes and keeps the viewBox attribute, which leaves the icon scalable. The prefixIds plugin prefixes IDs and class names with svg- and the file name, so references such as gradients and filters stay unique when several SVGs share one document.
After optimization, the icon’s CSS looks like this:
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' viewBox='0 0 20 20'%3E%3Cpath d='M18.672 11H17v6c0 .445-.194 1-1 1h-4v-6H8v6H4c-.806 0-1-.555-1-1v-6H1.328c-.598 0-.47-.324-.06-.748L9.292 2.22c.195-.202.451-.302.708-.312.257.01.513.109.708.312l8.023 8.031c.411.425.539.749-.059.749z'/%3E%3C/svg%3E");Performance
If you are curious how SVG icons embedded this way affect performance, see the comparison page I created at https://svg-in-css-performance-test.vercel.app. In my opinion, with fewer than 100 SVG icons on a single page, the performance impact is minimal and acceptable. With hundreds of icons, however, it is best to avoid this method.