button { background: url('data:image/svg+xml,\ <svg viewBox="0 0 17 17"\ xmlns="http://www.w3.org/2000/svg">\ <path\ fill="white"\ d="M16.8 6.6l-4 4 .7 5.7-5-2.4-5.1\ 2.3.7-5.6L.3 6.5l5.5-1 2.8-5 2.6 5z"\ />\ </svg>\ '); }
Vector icons for web-apps can be implemented using SVG inlined into CSS with data URLs. This can improve performance vs using separate .svg files by avoiding extra network requests, and can be a simple, zero-dependency alternative to font-based or template-based icon insertion.
As serveral articles state (Stack Overflow, CSS-Tricks), certain characters must be URL-escaped and Base64 encoding is sometimes suggested as a workaround. For the payload of a data-URL however, only a subset of the usual characters reserved for URLs must be escaped. In many cases, escaping can be avoided altogether:
url('')
so double-quotes can be used for SVG tag attributes.rgb()
or color names like white
or black
instead of hex values to avoid the '#'
character, which would otherwise need to be percent-encoded.
CSS supports multi-line strings by appending a line continuation character ('\'
) to each line. This can allow for nicely indented SVG snippets. With the formatting kept more readable like this, it can be practical to type out SVG syntax for simple graphics directly into the stylesheet.
If you can accept the default character encoding for data URLs (US-ASCII
), you can omit an encoding declaration like ;utf8,
thus further reduding the boilerplate for each graphic. For the limited use-case of simple paths and lines, the default character encoding is enough.
Note that the SVG xmlns
attribute is required, but the version
attribute is ignored by browsers and can be omitted.
These data URLs can be pasted directly into your CSS as a background-image
url('data:image/svg+xml,\ <svg viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">\ <path stroke="white"\ stroke-width="0.15" stroke-linecap="round"\ d="M0.1,0.2 H0.9 M0.1,0.5 H0.9 M0.1,0.8 H0.9"\ />\ </svg>\ ') |
|
url('data:image/svg+xml,\ <svg viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">\ <path stroke="white" stroke-width="0.2"\ stroke-linecap="round" stroke-linejoin="round"\ d="M0.1,0.1 L0.9,0.9 M0.1,0.9 L0.9,0.1"\ />\ </svg>\ ') |
|
url('data:image/svg+xml,\ <svg viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">\ <path stroke="white" stroke-width="0.2"\ stroke-linecap="round" stroke-linejoin="round"\ d="M0.1,0.5 H0.9 Z M0.5,0.1 V0.9"\ />\ </svg>\ ') |
|
url('data:image/svg+xml,\ <svg viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg">\ <g stroke="white" stroke-width="2">\ <circle cx="6" cy="6" r="4.5" fill="none"/>\ <path d="m9.4 9.4 6 6" stroke-linecap="round"/>\ </g>\ </svg>\ ') |
|
url('data:image/svg+xml,\ <svg viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">\ <path fill="white" d="M0.1,0 v1 L0.9,0.5"/>\ </svg>\ ') |
When creating more complex graphics or importing icons, I use a combination of Inkscape, SVGOMG and manual text editing to produce clean SVG markup suitable for inlining into CSS. Here's an example workflow using the Blender logo from Wikipedia:
This path has redundant vertices. The slightly smaller diamond-shaped handles indicate multiple verticies stacked onto the same position. If you click and drag you can pull out each one, revealing the duplication. It's a surprisingly common problem; possibly caused by conversion from one vector format to another.
There's also more verticies than necessary for the circular shapes; only four are needed per circle.
Inkscape's 'Simplify' path command can automatically clean up redundant verticies.
There's still a few redundant verticies that can be manually removed, after which the Bézier handles will need to be tweaked to ensure the overall shape is preserved.
Now it's quite tidy. If the logo were to be displayed at a very small scale, it could be simplified even further. In any case, the filesize will be small and it'll be as efficient as possible for the browser to render.
Save it as an "Optimized SVG" so it doesn't include any Inkscape-specific metadata which can be quite verbose. Several useful optimizations to reduce filesize further are enabled by default.
There's an option to limit the number of significant digits for coordinates, but I suggest leaving this at the default value and doing precision reduction in a separate step where you can visualize the result.
I use Jake Archibald's SVGOMG to visualize the result of applying precision reduction. You can find the optimum point before your paths become potato.
The additional optimization passes performed by SVGO may further simplify the SVG markup.
Here's the output from SVGOMG. It's pretty concise, but it needs a bit more cleaning before it can be inlined into a data URL.
<svg width="180.9" height="147.7" viewBox="18 268 180.9 147.7" xmlns="http://www.w3.org/2000/svg"><path d="M112.3 350.9c.3-6 3.2-11.1 7.5-14.8 4.3-3.7 10-5.9 16.3-5.9 6.3 0 12 2.2 16.4 5.9a21 21 0 017.5 14.8c.4 6-2 11.7-6.3 15.8a25 25 0 01-17.5 7 25 25 0 01-17.6-7 20.5 20.5 0 01-6.3-15.8z" fill="#005385" stroke-width="0"/><path d="M124.9 413c27.2 4.6 57.7-9.8 68.2-36a53.2 53.2 0 00-16.8-61.5L119.9 272c-5.8-5-19.2-.9-16.6 8 8 8 16.8 14.5 25 21.2l-71.5.1c-16.6 0-15.3 17.7.6 17.6H90c-21.6 16.6-43 33.5-64.9 49.8-12.7 9.6 2.4 26 14.2 16.1 9.6-8 25.8-20.4 35.5-28.3-4 31.4 30.2 53.1 50.2 56.3zm38.4-37.4c-15.8 16.6-46.3 14.8-59.5-4.1-10.6-13.8-6.4-35.2 7.8-44.9 18-13.9 48-8.4 58 12.5 6 12 3.2 27.2-6.3 36.5z" fill="#ff7021" stroke-width="0"/></svg>
The following manual edits were made to produce this final data URL:
'\'
).width
/height
attributes, as an intrinsic size isn't needed when the background-image
is sized relative to the parent element. The viewBox
determines the bounds of the svg.rgb()
to avoid the '#'
character, which would otherwise need to be percent-encoded.stroke-width
attribute, as stroke
already defaults to "none".url('data:image/svg+xml,\ <svg\ viewBox="18 268 180.9 147.7"\ xmlns="http://www.w3.org/2000/svg">\ <path fill="rgb(0,83,133)"\ d="M112.3 350.9c.3-6 3.2-11.1\ 7.5-14.8 4.3-3.7 10-5.9 16.3-5.9\ 6.3 0 12 2.2 16.4 5.9a21 21 0\ 017.5 14.8c.4 6-2 11.7-6.3\ 15.8a25 25 0 01-17.5 7 25 25 0\ 01-17.6-7 20.5 20.5 0\ 01-6.3-15.8z"/>\ <path fill="rgb(255,112,33)"\ d="M124.9 413c27.2 4.6 57.7-9.8\ 68.2-36a53.2 53.2 0\ 00-16.8-61.5L119.9\ 272c-5.8-5-19.2-.9-16.6 8 8 8\ 16.8 14.5 25 21.2l-71.5.1c-16.6\ 0-15.3 17.7.6 17.6H90c-21.6\ 16.6-43 33.5-64.9 49.8-12.7 9.6\ 2.4 26 14.2 16.1 9.6-8 25.8-20.4\ 35.5-28.3-4 31.4 30.2 53.1 50.2\ 56.3zm38.4-37.4c-15.8 16.6-46.3\ 14.8-59.5-4.1-10.6-13.8-6.4-35.2\ 7.8-44.9 18-13.9 48-8.4 58 12.5\ 6 12 3.2 27.2-6.3 36.5z"/>\ </svg>\ ')
background-image: url('data:image/svg+xml,\ <svg viewBox="0 0 35 35"\ xmlns="http://www.w3.org/2000/svg">\ <path\ fill="white"\ d="M16.8 6.6l-4 4 .7 5.7-5-2.4-5.1\ 2.3.7-5.6L.3 6.5l5.5-1 2.8-5 2.6 5z"\ />\ <polygon fill="rgb(0,160,0)"\ points="25,5 35,5 30,15"/>\ <circle fill="rgba(255,0,0,.5)"\ cx="25" cy="25" r="4"/>\ <rect fill="%236c71c4" rx="2"\ x="4" y="21" width="8" height="8" />\ </svg>\ ');
Full support | Partial support | No support | |
---|---|---|---|
Chrome | 80 70 65 64 60 50 40 37 | ||
Safari | 13 12.1 11.1 10.1 9.1 8 7.1 6.2 6 5.1 | 5 (1) | 4 |
Firefox | 75 74 70 66 52 51 45 32 | ||
Edge | 81 80 18 17 16 | 15 (2) | |
IE | (none) | 11 (2) 10 (2) 9 (2) | 8 7 6 |
Every major browser rendered the stress-test correctly, except IE: it requires full URL-encoding in the data URL. Given this, I'd say unencoded inline SVG is suitable for web apps that already require a modern browser, but not suitable where maximum compatiblity is a requirement, like a government website.
I've seen several Javascript-based methods of inlining vector icons into web apps, such as defining custom helpers for JS templating systems or using a React component to re-use SVG <symbol>
elements. Icon insertion methods that use Javascript to create 'real' SVG DOM elements can have disadvantages though:
Defining icons in pure CSS can allow for clean, semantically-correct HTML that's pleasant to inspect/debug, and minimizes performance bottlenecks.
Font Awesome, for example, provides a multitude of icons packaged as web fonts. It's easy to integrate and there's a wealth of customizations, but there's still a few disadvantages vs this pure-CSS solution: