Change an SVG's color by editing fill and stroke, either as attributes or through CSS. Simple in theory. In practice there are three places a color can be declared in the same file, they follow the normal CSS cascade, and if you don't know that, "I changed the fill and nothing happened" turns into a twenty-minute debugging session.
Here's the part of SVG color handling that usually doesn't get spelled out.
fill and stroke are separate properties
Every shape has an inside (fill) and an outline (stroke), set independently:
<circle cx="50" cy="50" r="40" fill="#3366ff" stroke="#000" stroke-width="2" />
Unset fill defaults to black. Unset stroke defaults to none. If an icon is pure fill with no stroke at all (most converted icon-font SVGs are), editing stroke-width is never going to do anything visible, and that's usually the first dead end people hit.
The cascade is the actual bug source
A color can come from three places, and they don't have equal priority:
- A presentation attribute:
<path fill="red" /> - An inline
styleattribute:<path style="fill: red;" /> - A
<style>block or external stylesheet:path { fill: red; }
Normal CSS specificity applies: style attribute beats stylesheet, stylesheet beats presentation attribute. Edit the fill="red" attribute directly, and if a <style> block elsewhere in the same file also targets that path, your edit is overridden and nothing changes on screen. No error, no warning, it just loses.
If a color edit isn't sticking, grep the file for <style before assuming your tool, or your edit, is broken. This one thing accounts for most "the SVG editor is buggy" reports that are actually the cascade working exactly as designed.
currentColor: SVG's inheritance trick
Set fill="currentColor" and the shape stops carrying its own color and instead inherits whatever color is set to on an ancestor element, the same mechanism that makes text inherit color:
<path fill="currentColor" d="..." />
.icon { color: #ff0000; }
<span class="icon"><svg fill="currentColor">...</svg></span>
Change .icon's color and the SVG updates with zero edits to the SVG itself. It's why most icon libraries ship currentColor by default: one file, infinite colors, controlled entirely by CSS at the call site. If you're publishing your own icon set, this is the property to reach for instead of hardcoding hex values into every export.
The one that actually surprises people: <img src>
<img src="icon.svg" />
The moment an SVG is referenced this way, the browser treats it as an opaque raster-like image. There's no DOM access, no CSS targeting fill, and currentColor resolves to nothing because there's no inheritance path across the document boundary. filter: invert() gets you a rough approximation at best, not a real color swap.
Three actual fixes, in order of how much they cost you:
Inline the SVG in the HTML. Once the markup is literally on the page, it's a normal element and every CSS trick above works.
Use it as a mask instead of a source:
.icon {
background-color: currentColor;
mask: url(icon.svg) center / contain no-repeat;
-webkit-mask: url(icon.svg) center / contain no-repeat;
}
The shape becomes a stencil, the visible color comes from background-color, and you never touch the SVG file.
Use a sprite with <use>, setting fill on the <use> element, assuming the source paths don't already have a hardcoded fill fighting you (see the cascade section above, same rule applies here too).
Multi-path SVGs need more than one edit
A two-tone logo is rarely one shape. It's usually four or five <path> elements, each carrying its own fill. Changing one and expecting the whole icon to shift is the second-most common gotcha after the cascade issue. Either:
- put
fill="currentColor"on every path that should track the parent's text color, or - set
fillon a wrapping<g>, keeping in mind any child path with its own explicitfillstill wins over the group value
Past two or three paths this gets tedious fast when you're doing it by hand in a text editor, hunting fill= occurrences one at a time with no visual feedback on which shape you're actually touching.
CSS custom properties work fine inside SVG
<svg>
<style>
:root { --icon-color: #3366ff; }
.body-fill { fill: var(--icon-color); }
</style>
<path class="body-fill" d="..." />
</svg>
One variable, referenced by every shape that shares it. If the SVG is inlined (not loaded via img), the page's own CSS can override that variable directly, giving a multi-part graphic a single external color hook instead of duplicated hex values scattered across paths.
Doing this visually
SVG Lab is a free browser SVG editor I built: visual fill and stroke editing plus path and node editing (bezier control points, anchor manipulation), no install. It doesn't replace knowing how the cascade works, it just means you click the shape you want to change instead of hunting through markup for it.
Top comments (0)