Use SVG as a CSS Background Image — Inline with Data URIs
Published May 28, 2026 · 5 min read
CSS's background-image property accepts any URL, including a data: URI. That means you can embed an entire SVG directly inside a stylesheet — no extra HTTP request, no separate asset pipeline, no sprite map. For small icons and decorative shapes, this is almost always the right move.
Why SVGs need special handling in background-image
Unlike PNG or JPEG, SVG is XML. It contains characters that are reserved in URLs — #, %, <, >, quotes, and newlines. Paste a raw <svg> directly into background-image: url("...") and browsers will either fail to parse it or silently drop the rule. The SVG must be encoded so the URL parser sees valid characters end to end.
Three ways to ship an SVG to CSS
- External file —
background-image: url("/icon.svg"). Simple, cacheable, but costs a request. Hard to recolor without filters or fragment identifiers. - Data URI (inline) — embed the encoded SVG directly in the stylesheet. Zero extra requests. Trivially themeable: change the fill in the source SVG, re-encode, paste.
- SVG sprite — one file with multiple
<symbol>definitions referenced by fragment. Great for<use>in HTML, awkward forbackground-image.
Why data URIs win for small SVGs
Anything under a few KB pays more in connection overhead than it saves by being cached separately. Inlining removes the request, blocks no rendering, and the stylesheet itself is already gzipped and cached. The icons ship with the CSS that uses them — no version drift, no 404s when an asset path changes.
URL-encoded vs Base64 (briefly)
Both produce a valid data: URI. URL-encoding preserves SVG's text and gzips well — it adds only ~3–12% overhead for typical icons. Base64 adds a flat ~33% and produces opaque binary-looking output. For SVG, always prefer URL-encoding. We covered the byte-level details in Data URI vs Base64 for SVG.
Same check icon, two encodings:
| Encoding | Size | Overhead vs raw |
|---|---|---|
| Raw SVG | ~224 B | — |
| URL-encoded data URI | ~244 B | ~9% |
| Base64 data URI | ~308 B | ~37% |
Step-by-step: any SVG → CSS
- Copy your SVG markup (from Figma, an icon set, or hand-written).
- Open svgencoder.com and paste it into the input pane.
- Switch to the CSS tab in the output. You'll see a complete
background-imagerule using a URL-encoded data URI. - Copy the rule and paste it into your stylesheet.
Complete working example
A button with an inline SVG checkmark as the leading icon — no external assets:
/* button.css */
.btn-confirm {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.875rem 0.5rem 2rem;
font-weight: 500;
color: #fff;
background-color: #16a34a;
border-radius: 0.5rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: 0.5rem center;
background-size: 1rem 1rem;
}Note the %23 in front of ffffff — that's an encoded #. Forgetting to encode the hash is the single most common reason an inline SVG silently fails.
Recoloring with mask-image
If you need a single icon that adapts to currentColor or theme tokens, use mask-image instead of background-image. The SVG becomes a stencil; the visible color comes from background-color:
.icon-check {
width: 1rem;
height: 1rem;
background-color: currentColor;
-webkit-mask-image: 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.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
mask-image: 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.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}Now color: var(--accent) on the parent recolors the icon. The encoded stroke color inside the SVG doesn't matter — masks only read alpha.
Performance notes
- Gzip is your friend. URL-encoded SVG compresses extremely well because it's still text. Two identical icons used in many rules dedupe at the transfer layer.
- Caching cutoff. Inlining trades a cacheable asset for stylesheet bytes. For icons reused across pages, that's a win — the CSS is cached anyway. For a 40 KB illustration used on one page, ship it as an external
.svgso it can be lazy-loaded and cached independently. - Rule of thumb: inline SVGs under ~4 KB; externalize anything larger or anything you'll animate.
Browser support
background-image with a data: URI works in every browser shipped since IE8. mask-image needs the -webkit- prefix for Safari but is otherwise universal in evergreen browsers. There is no runtime cost beyond parsing the stylesheet.
Further reading
- MDN — Data URIs
- CSS-Tricks — Probably Don't Base64 SVG
- Data URI vs Base64 for SVG — Which Encoding to Use
Paste any SVG into svgencoder.com, grab the CSS tab, and you're done. Everything runs locally — the SVG never leaves your browser.