Skip to content
SVGtoReact

SVG to Data URI in CSS: Encoding Rules

Put an SVG to data URI in CSS correctly: which characters to percent-encode, UTF-8 versus base64, quoting, recoloring with mask-image, and CSP rules.

By SVG to React. Published . Updated .

A data URI puts a file directly inside a URL, so an SVG can live in a stylesheet with no separate request. This works well for small decorative images such as list bullets, select arrows, dividers, and simple icons. It also breaks in confusing ways when the SVG is pasted in without encoding. This post covers the syntax, the characters that must be escaped and why, the base64 question, and a pattern for icons that follow the text color.

The syntax

A data URI has the form data:[media type][;base64],data. For SVG the media type is image/svg+xml, and the data can be the SVG markup itself, percent-encoded where needed:

styles.css
.badge {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%230f766e' d='M8 1 1 15h14z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 1rem 1rem;
}

The SVG must include xmlns="http://www.w3.org/2000/svg". Inline SVG in HTML works without it because the HTML parser puts the element in the SVG namespace. A data URI is parsed as a standalone XML document, and without the namespace the browser does not treat it as SVG at all, so nothing renders. Converters that produce React components often remove xmlns, so do not reuse that output here.

Which characters to encode

The data part is a URL, so characters with a meaning in URLs must be escaped as % followed by two hex digits. Encoding everything with encodeURIComponent works, but it makes the result longer and unreadable. In practice a short list is enough:

Here is the failure from the first bullet, the most common data URI bug:

Broken
/* Broken: the first # starts a URL fragment, so the image ends at fill=' */
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><path fill='#0f766e' d='M0 0h8v8H0z'/></svg>");

A small encoder

This function applies the rules above in the right order:

svg-to-data-uri.js
export function svgToDataUri(svg) {
  const encoded = svg
    .trim()
    .replace(/\s+/g, " ")   // collapse newlines and indentation
    .replace(/"/g, "'")      // so the CSS can use double quotes
    .replace(/%/g, "%25")    // first, so later escapes are not double-encoded
    .replace(/#/g, "%23")
    .replace(/</g, "%3C")
    .replace(/>/g, "%3E");
  return `url("data:image/svg+xml,${encoded}")`;
}
Usage and real output
svgToDataUri(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path fill="#0f766e" d="M8 1 1 15h14z"/>
</svg>`);

// url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E %3Cpath fill='%230f766e' d='M8 1 1 15h14z'/%3E %3C/svg%3E")

The quote swap assumes the SVG has no single quotes in its attribute values or text. If it does, encode double quotes as %22 instead of swapping them. Collapsing whitespace also changes runs of spaces inside <text> elements, which SVG collapses by default anyway. The SVG to Data URI converter encodes in your browser, as UTF-8 or base64. It sanitizes the SVG first, shows both sizes, and can also decode an existing data URI.

URL-encoded or base64?

styles.css
/* Same image as base64: valid, but about a third larger before compression */
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...");

Base64 encodes every three bytes as four characters, so it is about 33 percent larger than the input. URL-encoded SVG is usually much closer to the original size, because most characters in SVG markup pass through unchanged. Gzip and Brotli narrow the difference but do not remove it, since base64 hides the repetition in the markup. Base64 has one advantage: it never needs quote or character escaping, which makes it robust in places such as inline style attributes generated by code. For hand-written CSS, prefer the URL-encoded form. It is smaller and you can still read the shapes and colors.

Data URIs cannot inherit currentColor

An image loaded from a data URI is a separate document. It cannot see the page's CSS, and currentColor inside it refers to its own default color, not the color of the element using it. Changing the icon color then means a second data URI with another fill. CSS masks solve this. Use the SVG as the mask shape, and paint the element with background-color: currentColor:

icon-mask.css
.icon-check {
  display: inline-block;
  width: 1em;
  height: 1em;
  background-color: currentColor;   /* the icon takes the text color */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'%3E%3Cpath d='m5 12 5 5L20 7'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'%3E%3Cpath d='m5 12 5 5L20 7'/%3E%3C/svg%3E") center / contain no-repeat;
}

.button:hover .icon-check { color: #0f766e; }

Only the alpha of the mask matters, so the SVG can be drawn in black. The icon now follows the text color, including hover states and themes. Unprefixed mask is supported in current browsers. The -webkit-mask line keeps older Safari and Chromium versions working. If you need more than one color, use an inline SVG or a component instead. The SVG to React component guide covers that route.

Content Security Policy

A strict CSP can block data URIs. Images, including CSS backgrounds, fall under img-src, and data: must be listed explicitly. 'self' does not cover it:

HTTP header
Content-Security-Policy: default-src 'self'; img-src 'self' data:

Masks are covered by img-src too. Allowing data: for images is low risk. Browsers do not run scripts in an SVG used as an image, so a data URI image cannot execute code on your page.

When not to use a data URI

Before encoding, run the file through the SVG optimizer. Editor metadata and long coordinates cost more inside a stylesheet than anywhere else. If a target does not accept SVG at all, such as some email clients or app store assets, convert it with SVG to PNG instead.

Questions

Why does my data URI break at a color like #fff?

The # character starts the fragment part of a URL, so everything after it is not read as image data. Encode it as %23. This is the most common reason an unencoded SVG in url() renders as nothing.

Is base64 or UTF-8 better for SVG in CSS?

For SVG, percent-encoded UTF-8 is usually the better choice. Base64 turns every 3 bytes into 4 characters, so it is about a third larger before compression, and it hides the markup from anyone reading the stylesheet. Base64 is still fine when a tool requires it.

Can a data URI SVG use currentColor from my page?

No. An SVG loaded through url() is a separate image document, so it cannot see the color of the element that uses it. Use it as a mask-image and set background-color: currentColor on the element to get the same effect.

Does the xmlns attribute matter in a data URI?

Yes. Inline SVG in HTML gets its namespace from the parser, but a data URI is parsed as a standalone XML document. Without xmlns="http://www.w3.org/2000/svg" on the root element the browser does not treat it as SVG, and nothing is drawn.