How to css custom properties in svg: browser compatibility and faq
- Step 1Confirm the SVG is inline — Open dev tools and check whether the
<svg>tag is in the DOM or loaded via<img src>/background-image. Variables only resolve for inline SVG. This one check resolves most failures. - Step 2Verify the variable is actually defined and reachable — Add a temporary
border: 1px solid var(--icon-fg)to a parent element. If the border shows the colour, the variable is defined and cascading — so the problem is in the SVG, not the declaration. - Step 3Confirm the colour was rewritten to var() — Inspect the SVG markup. If a path still shows
fill="#003082"instead offill="var(--…)", the injector didn't target it — likely because it was a named colour,rgb(), or a shorthand the browser tool didn't expand. - Step 4Check for a competing CSS rule — A CSS rule like
svg path { fill: #000; }can override yourvar()presentation attribute. Search your stylesheets forfill/strokerules hitting the icon and make sure none win over the variable. - Step 5Check the declaration scope — A variable defined inside a component-scoped CSS Module may not reach the SVG. Move theming variables to global CSS on
:rootor a shared ancestor element. - Step 6Add a fallback to rule out 'undefined variable' — Temporarily change the SVG to
var(--icon-fg, magenta). If the icon turns magenta, the variable is undefined at that point in the cascade; if it shows the right colour, the variable is resolving fine.
Browser support for CSS custom properties in inline SVG
Custom properties (CSS Variables) are a long-shipped feature. Support shown is for the variables mechanism that inline SVG relies on; IE11 has no support at all.
| Browser | Custom properties since | Notes for SVG |
|---|---|---|
| Chrome / Edge (Chromium) | Chrome 49 / Edge 16 | Full support for var() in inline SVG presentation properties |
| Firefox | 31 | Full support; inline SVG inherits document variables |
| Safari | 9.1 | Full support; same cascade rules as HTML |
| IE11 | Not supported | No CSS custom properties — use hardcoded hex or a build-time theme |
Troubleshooting matrix: variable not applying
Symptom → likely cause → fix. The first three rows are platform/cascade issues; the last three are this injector's behaviour.
| Symptom | Likely cause | Fix |
|---|---|---|
| No theming at all | SVG used as <img> / background-image | Inline the SVG markup in the HTML |
| Some icons themed, some not | Variable defined in a scoped CSS Module | Move theme variables to global :root |
| Variable defined but colour wrong | A CSS fill/stroke rule overrides the var | Remove the competing rule or raise specificity on the var |
Colour still shows as #hex | Source was a named colour or rgb() | Normalise to hex with svg-hex-swapper, then re-inject |
| Shorthand colour untouched (browser tool) | Picker emits 6-digit; #03f not pre-expanded | Convert the SVG to 6-digit hex first |
| Looking for a generated CSS file | Tool outputs the SVG only | Write the :root block yourself |
Cookbook
Minimal working and broken setups you can diff against your own. CSS blocks are author-written; the injector only produces the SVG.
Working: inline + global :root
The canonical setup. SVG inlined, variable on :root, optional fallback for safety.
<style>:root{--icon-fg:#1a1a2e}</style>
<svg viewBox="0 0 24 24">
<path fill="var(--icon-fg, #1a1a2e)" d="..."/>
</svg>
→ icon renders #1a1a2e and re-themes when --icon-fg changes.Broken: SVG used as <img>
Identical SVG, but referenced externally — variables can't resolve.
<style>:root{--icon-fg:#1a1a2e}</style>
<img src="icon-css-vars.svg">
→ var(--icon-fg) has nothing to resolve against;
the icon renders with no fill (or its fallback).
Fix: inline the <svg> markup instead of using <img>.Broken: variable trapped in a CSS Module
The variable is declared in scoped module CSS that never cascades to the SVG.
/* Card.module.css (scoped) */
.card { --icon-fg:#1a1a2e; } <-- doesn't reach the SVG
Fix — global CSS:
/* global.css */
:root { --icon-fg:#1a1a2e; }Broken: competing fill rule wins
A stylesheet rule overrides the variable-based presentation attribute.
<style>
:root { --icon-fg:#1a1a2e; }
svg path { fill:#000; } <-- this wins over the attribute
</style>
<svg><path fill="var(--icon-fg)" d="..."/></svg>
→ icon is black, not --icon-fg.
Fix: drop the `svg path { fill }` rule.Diagnose: colour never became a variable
If the markup still has a literal hex, the injector didn't target it — check the colour form.
Still hardcoded after injection: <path fill="red"/> (named — not detected) <path fill="rgb(0,48,130)"/> (rgb — not detected) <path fill="#03f"/> (shorthand — browser tool miss) Fix: convert to 6-digit hex (svg-hex-swapper), then re-run the injector.
Edge cases and what actually happens
Variables don't resolve in an externally referenced SVG
ExpectedAn SVG loaded via <img>, background-image, or (in most cases) <object> is an isolated document. The page's :root custom properties don't cross that boundary, so var(--…) resolves to nothing. Inline the SVG to give it access to the document cascade.
Variable defined in scoped CSS doesn't reach the icon
Scope issueCustom properties declared in a component-scoped CSS Module (or a deeply nested selector that doesn't include the SVG's ancestor) won't cascade to an inline SVG elsewhere. Declare theming variables globally on :root or on a shared ancestor of the icon.
A CSS rule overrides the var() presentation attribute
Cascade conflictfill="var(--x)" is a presentation attribute, which a CSS fill: rule can override. If a stylesheet sets svg path { fill: … }, it wins and your variable is ignored. Keep colour authority in one place — remove the competing rule or move the colour into the variable-driven CSS.
Colour still shows as literal hex after injection
Not detectedThe injector targets #hex strings. If a colour is a named keyword (red), a functional notation (rgb()/hsl()), or (in the browser tool) 3-digit shorthand the picker didn't match, it's left as-is. Normalise to 6-digit hex with svg-hex-swapper and re-inject.
No CSS file appears alongside the SVG
By designThe tool's output is the rewritten SVG only (*-css-vars.svg). There is no generated declarations file. Author the :root { … } block yourself; this is expected, not a bug.
Undefined variable renders a blank shape
Add fallbackBare var(--icon-fg) with no declaration and no fallback yields an empty fill — often an invisible icon. Add a fallback, var(--icon-fg, #1a1a2e), so the shape always has a colour even before (or without) the theme CSS.
IE11 / very old browsers
UnsupportedInternet Explorer 11 and similarly old engines have no CSS custom-property support, so var() colours won't resolve anywhere, SVG included. If you must support them, ship hardcoded-hex icons or generate per-theme builds instead of runtime variables.
Same hex used by elements you didn't mean to change
ExpectedReplacement is value-based and global, so every occurrence of a targeted hex becomes the same variable — even on shapes you intended to keep separate. If two roles share a hex, disambiguate the colours in the source before injecting.
Frequently asked questions
Which browsers support CSS custom properties in inline SVG?
All current major browsers: Chromium-based (Chrome 49+/Edge 16+), Firefox 31+, and Safari 9.1+. They apply the same cascade and inheritance to inline SVG as to HTML. IE11 has no custom-property support at all.
Why isn't my CSS variable updating the SVG fill?
The three usual causes: (1) the SVG is loaded as <img>/background, not inlined; (2) a CSS fill/stroke rule overrides the variable-based presentation attribute; (3) the variable is declared in a scope (e.g. a CSS Module) that doesn't cascade to the icon. Work through them in that order.
How do I tell if the variable or the SVG is the problem?
Put var(--icon-fg) on a non-SVG element's border temporarily. If the border shows the colour, the variable resolves and the issue is inside the SVG (often a competing rule or an unreplaced colour). If the border is wrong too, the variable itself isn't reaching that scope.
My colour is still a hex after running the tool — why?
The injector only rewrites #hex strings. Named colours (red), rgb()/hsl(), and — in the browser tool — 3-digit shorthand that the 6-digit picker didn't match are left untouched. Normalise to 6-digit hex with svg-hex-swapper, then re-inject.
Does the tool give me a CSS file with the variable values?
No. It outputs the rewritten SVG only. You author the :root { --name: #hex; } declarations. This trips up a lot of first-time users, so plan for it: the tool sets up the references, you supply the values.
Can I use variables in clip-path, filter, and gradient definitions?
Yes, in inline SVG. Custom properties cascade into elements inside <defs>, <clipPath>, <filter>, and <linearGradient> just like any other. The injector will turn a stop-color/flood-color hex into a variable when you map that hex.
Does SVG inheritance differ from HTML?
Some SVG properties (fill, stroke, color) are inheritable, so children inherit from parent SVG elements. CSS custom properties follow the standard CSS inheritance model, which applies to SVG elements the same way as HTML — define the variable on an ancestor and descendants pick it up.
Why does a CSS rule beat my var() attribute?
Presentation attributes (like fill="var(--x)") sit at the bottom of the cascade — any author CSS rule targeting the element's fill overrides them. If you need the variable to win, move the colour into a CSS rule too (e.g. path { fill: var(--x); }) or remove the competing rule.
What's the safest way to avoid undefined-variable bugs?
Always include a fallback: var(--icon-fg, #1a1a2e). The tool emits bare var(--name), so add the fallback to the output. It guarantees a sensible colour even if the declaration is missing or hasn't loaded yet.
Is there any performance concern with many SVG variables?
No meaningful one for normal icon counts — variable resolution is cheap. The practical concern is maintenance: keeping your :root declarations in sync with the variables your SVGs reference. Drive both from a single token source where possible.
Can I check compatibility without changing my real SVG?
Yes — run the injector on a copy, inline it in a scratch HTML file with a :root declaration, and toggle the value in dev tools. If it re-themes there, the mechanism is sound and any production failure is environmental (delivery method or scope).
What if I need IE11 or a no-JS, no-variables fallback?
Generate hardcoded-hex copies for those targets (e.g. a light and a dark build) instead of relying on runtime variables. You can produce the variants by swapping colours with svg-hex-swapper. Reserve the variable approach for modern, inline contexts.
Privacy first
Every JAD SVG tool runs entirely in your browser using the DOM API and Canvas. Your SVG files never leave your device — verified by zero outbound network requests during processing.