You inline an icon to save a request. Two months later your stylesheet is 400 KB, first paint has slipped, and nobody can see why. Base64 images in CSS are a real technique with a real cost, and most guides only tell you half of it.
Here is the whole picture: how to do it, what breaks, and when to stop.
How a Base64 Image in CSS Actually Works
A Data URI puts the file inside the URL rather than pointing at one. The browser reads the bytes straight from your stylesheet:
.icon-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANS...");
background-size: 16px 16px;
background-repeat: no-repeat;
}
Four parts do the work. data: names the scheme, image/png is the MIME type, ;base64 says the payload is encoded, and everything after the comma is the file.
Always wrap the value in quotes. Unquoted url() has its own escaping rules and they will surprise you.
Generate the string with our image to Base64 converter — it outputs a finished CSS declaration, so there is nothing to assemble by hand.
Inline Images in HTML with an img src
The markup version is simpler, because HTML has none of the CSS escaping problems:
<img src="data:image/png;base64,iVBORw0KGgoAAAANS..." alt="Search">
Write real alt text. A Data URI is opaque to every assistive technology, so the alt attribute is the only description a screen reader will ever get.
One practical difference from CSS: an inline image in HTML does not block rendering the way a stylesheet does. The browser paints around it. That makes HTML the safer place to inline when you have the choice.
Data URI in a CSS Background: The Escaping Traps
Base64 output is alphanumeric, so it survives anything. SVG is where people get hurt, because percent-encoded SVG is smaller and everyone wants to use it.
| Character | Escape as | What breaks if you forget |
|---|---|---|
| # | %23 | Every hex colour truncates the URL |
| % | %25 | Corrupts all other escapes |
| < > | %3C %3E | Every tag in the file |
| { } | %7B %7D | Embedded style blocks |
| " | Use ' instead | Closes the url() early |
The hash is the one that catches everyone. An SVG with a single fill="#333" in it will silently render nothing.
Our SVG to Base64 encoder produces both encodings side by side and escapes all of this for you.
The Caching Cost Nobody Mentions
This is the argument that should decide it, and it is the one usually left out.
A normal image file is cached on its own. First visit downloads it; every later visit and every other page reuses it from disk. That is one download, ever.
An inlined image is part of the stylesheet. Change one CSS rule and the whole file — every embedded image with it — is downloaded again. Split the image across two stylesheets and it downloads twice.
In our experience this is where inlining quietly loses. The saved request looks good on a first-load waterfall and costs you on every visit after that.
The 33% size penalty compounds it. A 2 KB icon costs about 700 extra bytes, which is nothing. A 200 KB illustration costs 66 KB, which is not.
The CSP Rule That Blocks Everything
One that catches teams the first time they harden a site:
Content-Security-Policy: img-src 'self'
That policy blocks every Data URI on the page. The browser console shows a CSP violation, not a broken image, so the first instinct is to blame the encoding.
The fix is to allow the scheme explicitly:
Content-Security-Policy: img-src 'self' data:
Do it deliberately, though. Allowing data: in script-src or object-src is a genuine security hole, and copying the pattern across directives is how that happens.
When Not to Inline
Our rule of thumb: under 2 KB inline freely, 2 to 10 KB think about it, above 10 KB do not.
Skip inlining entirely when any of these apply:
- The image appears on many pages. A cached file wins every time.
- It is your LCP element. Hero images need to start downloading early, not wait for CSS to parse.
- You use responsive images. Srcset and Data URIs work against each other.
- It is going in an email. Outlook strips Data URIs.
- The asset changes often. Every update invalidates the whole stylesheet.
And be honest about HTTP/2. The original case for inlining was request overhead, and multiplexing removed most of it. The technique survived the reason for it.
Where Inlining Still Wins
Four cases where we still reach for it:
- Tiny critical icons needed for the first paint.
- Single-file HTML — reports and exports that must work with no network.
- Generated content such as QR codes and charts that exist only for one page.
- Offline-first apps where every asset ships in the bundle.
Notice the pattern. Inlining is for images that are small, stable, and needed immediately.
Wrapping Up
Using a Base64 image in CSS is easy. Knowing when not to is the skill.
Inline the small, critical, unchanging things. Host everything else and let the browser cache do its job. When you do inline an SVG, escape the hash characters or watch it disappear.
For the numbers behind the trade-off, see when Base64 images are worth it. To generate the strings, use our PNG to Base64 encoder, and to read one back, the Base64 image decoder.
