Inline SVG in CSS

          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.

Encoding

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:

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.

Examples

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>\
              ')
            

Inkscape workflow

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:

Blender logo opened in Inkscape

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 Simplify path menu item

Inkscape's 'Simplify' path command can automatically clean up redundant verticies.

Simplified Blender logo in Inkscape

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.

Blender logo path manually cleaned up in Inkscape

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.

Inkscape save as Optimized SVG dialog

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.

Blender logo opened in SVGOMG

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:

  • Add line breaks with line continuations ('\').
  • Remove 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.
  • Convert hex colors to rgb() to avoid the '#' character, which would otherwise need to be percent-encoded.
  • Remove the 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>\
          ')
        

Browser support

The following stress-test uses various SVG elements and color encodings, and was applied to a range of browser versions using BrowserStack:
          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
(1)
Translucently isn't handled correctly
(2)
Must be fully URL-encoded

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.

Compared to JS-based SVG insertion

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.

Compared to font-based icons

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:

Gotchas and limitations