tl;dr: You can stop worrying and URL-encode only the #
character.
What?
So you want to have an SVG image in a CSS stylesheet. Yup, using data URIs (hey lookie, a 2009 post). There are a number of reasons not to embed images in CSS to begin with (caching, reuse), but hey, sometimes you're not in a position to make that particular call.
Base64-encode
One way to go about it is to base64-encode the SVG, like:
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=')
Drawbacks: Base64 makes the content larger by 25-30%. And also a human reading the code cannot tell what's in the image. (A nice feature of SVG is being able to tell what's in an image, roughly)
URL-encode
Another way to include an SVG is to use URL encoding:
background: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%2F%3E')
Drawback: the image payload is even larger. All these %20
(spaces) and %3C
(<) characters quickly add up. The SVG is a bit more readable though.
As-is
A quick bit of testing in modern browsers suggests that the browser can understand the unencoded SVG perfectly well, so how about a new solution: SVG as-is.
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"></svg>')
It all works fine for this particular type of MVP SVG but, as I quickly found out, as soon as you add a color like fill="#bad"
to the SVG, the # acts like a URL hash and everything after it is no longer part of the SVG. So the image appears broken.
Selective URL-encoding
I was curious what other characters may brake the image and I looked and I asked around. In the webperf slack, Radu pointed to Sass/Bootstrap that's been around forever, it's battle-tested, (m)old-browser-verified and so on. Sass escapes these characters:
< becomes %3c > becomes %3e # becomes %23 ( becomes %28 ) becomes %29
I see the #hash is there, but the other characters? Digging through github I saw that the encoding was added 5 years ago containing initially only the characters <>#
. The parentheses were added later to avoid a bug in a CSS minifier.
If we use a decent CSS minifier, we can forget about )(
and we're left with <>#
. I cannot find a reputable source for <>
but the rumor has it it's for IE support. Well, IE is no more. We're left with only #
to worry about.
# encoding
And here's the conclusion: in this day and age, encode your #
(replace with %28
) and enjoy small payloads, readable SVGs and modern browser support.
Future-proof?
The only nagging thing is that MDN will tell you to URL-encode. That's the right way. The fact that browsers are tolerant may be only temporary. But, if browsers suddenly decide to be strict, that'd be a Web-breaking change. Because a zillion web pages have partially encoded SVGs, I mean Sass/Bootstrap is popular. And browsers go to great lengths to avoid breaking the web. So I think it's safe to assume this minimal #-encoding will work for a looong time.
p.s. And, of course, escape the quotes you use in the url()
. I'd suggest using single quotes, so the SVG itself can use double quotes. All of these should be ok though:
background: url('data:image/svg+xml,<svg xmlns=\'http://www.w3.org/2000/svg\'></svg>'); background: url("data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>"); background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"></svg>'); background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>");
p.p.s. Apologies about the cheesy "the Truth" in the title. I'm aiming to be a tad obnoxious to trick people into proving me wrong. The Truth is what I'm seeking even if I'm wrong temporarily.
Comments? Find me on BlueSky, Mastodon, LinkedIn, Threads, Twitter