There are two ways to change an icon's colour, and choosing between them is the whole job.
Set it before download. Use the tool, pick a colour, export. Right when you are handing the file to a designer, placing it in a document, or exporting a PNG, since a PNG's colour is permanent.
Set it in CSS. Download the SVG neutral and let your stylesheet drive it. Right for anything on the web, because one icon then handles every state and every theme.
The rule that explains most confusion
An SVG can only be recoloured with CSS if it is inline in your HTML. An SVG loaded through <img>, a CSS background-image, or an <object> is a separate document that your page styles cannot reach into. This one fact accounts for most "why won't my icon change colour" questions.
currentColor, the mechanism
currentColor is a CSS keyword meaning "whatever the computed color property is here". Icon sets ship with it in place of a hard-coded colour, which is what makes them themeable.
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
.toolbar { color: #444; } /* icon is #444 */
.toolbar a:hover { color: #FF5A36; } /* icon follows, no icon CSS */
The important part is that color inherits. Set it on a container and every icon inside picks it up. Set it on a button and the icon matches the label automatically, which is exactly what you want and what hard-coded fills prevent.
fill or stroke: knowing which
Setting the wrong one is the single most common icon styling bug, and it produces two very recognisable failures.
| Icon type | Sets | Colour with | Wrong property gives you |
|---|---|---|---|
| Stroke-based | Lucide, Tabler, Feather | stroke, with fill: none | A solid black blob |
| Fill-based | Material, Simple Icons, Phosphor fill | fill | No visible change |
How to tell them apart in two seconds: open the SVG and look at the root element. fill="none" stroke="currentColor" means stroke-based. fill="currentColor" with no stroke attribute means fill-based.
The global rule that breaks everything
svg { fill: currentColor; } in a stylesheet is a very common line and it turns every stroke-based icon on the page into a filled shape. If your Lucide icons suddenly render as solid blobs, search your CSS for this. Scope it to a class instead of applying it to every SVG.
Hover, focus, active, disabled
With currentColor, every state is a colour change on the parent and nothing else.
.icon-btn { color: #6B7280; }
.icon-btn:hover { color: #111827; }
.icon-btn:focus-visible { color: #FF5A36; outline: 2px solid currentColor; }
.icon-btn[disabled] { color: #D1D5DB; }
Two things worth doing properly here. Use :focus-visible rather than :focus, so keyboard users get a clear indicator without mouse users getting an outline on every click. And do not rely on colour alone for disabled state, since a low-contrast icon is invisible to some users; pair it with aria-disabled and a cursor change. More in accessible icons.
Dark mode with zero extra files
Because icons inherit color, dark mode requires no icon work at all. Set your text colour per theme and the icons follow.
:root { --fg: #111; }
[data-theme="dark"] { --fg: #EEE; }
body { color: var(--fg); }
One adjustment worth making: pure white icons on a dark background can look heavier than the same shape in black on white, an effect called irradiation. Using a slightly off-white, around #E8E8E8, often reads better than #FFFFFF. Material Symbols exposes a whole grade axis for exactly this problem.
CSS variables for icon colour
Once you have more than a handful of icon contexts, define the colours as tokens rather than repeating hex values.
:root {
--icon-default: #6B7280;
--icon-active: #FF5A36;
--icon-danger: #DC2626;
--icon-muted: #D1D5DB;
}
.icon { color: var(--icon-default); }
.icon--active { color: var(--icon-active); }
.icon--danger { color: var(--icon-danger); }
The payoff is that rebranding, adding a theme or adjusting contrast for accessibility becomes a change in one block rather than a search across a codebase. It also gives your design and engineering teams shared vocabulary, which is most of what a design system is.
When the SVG is in an img tag
You cannot recolour it. The SVG is a separate document and CSS does not cross that boundary. Four ways out, in order of preference:
- Inline it instead. The correct fix in almost every case.
- Use an SVG sprite with
<use>. Keeps one shared file while remaining stylable, as long as the sprite's shapes usecurrentColor. - Use CSS
mask-image. The SVG becomes a mask andbackground-colorbecomes the icon colour. Works for single-colour icons and is well supported now. - Export a second file in the colour you need. Crude, but perfectly reasonable for one or two static icons.
The mask technique, since it is less known:
.icon-mask {
width: 24px; height: 24px;
background-color: currentColor;
-webkit-mask: url(icon.svg) center / contain no-repeat;
mask: url(icon.svg) center / contain no-repeat;
}
Note the CSS filter approach you will find in older answers, chaining invert, sepia, saturate and hue-rotate to approximate a colour. It is imprecise, unreadable, and there is no reason to use it now that masks are supported.
Multi-colour icons
currentColor gives you one colour. For two or more, either target the shapes individually, which requires inline SVG and shapes you can address, or use CSS variables inside the SVG itself:
<svg viewBox="0 0 24 24">
<path d="..." fill="var(--icon-primary, #FF5A36)"/>
<path d="..." fill="var(--icon-secondary, #FFD9CF)"/>
</svg>
Now the page controls both colours through variables, with sensible fallbacks if they are not set. This is a clean pattern for duotone icons, and it is roughly how Phosphor's duotone weight is intended to be handled.
Brand logos are the exception: do not recolour a full-colour brand mark to fit your palette, since most brand guidelines prohibit it. Use the monochrome version of that mark instead. See brand logos.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Nothing changes | SVG is in an img tag or background | Inline it, or use mask-image |
| Icon is a solid blob | fill set on a stroke icon | Set fill: none, colour the stroke |
| Only part changes | Some shapes have hard-coded fills | Replace those with currentColor |
| Colour ignored | Inline attribute beating your CSS | Remove the attribute, or raise specificity |
| Works locally, not in production | An SVG optimiser inlined the colours | Configure it to preserve currentColor |