web3icons

currentColor: SVG icons that follow your text color

How fill set to currentColor makes a mono SVG icon follow the CSS text color through hover, focus and dark mode, and why it only works for inline SVG.

5 min read

You style a link gray, turn it white on hover, and want the little chain icon inside it to change with the text. Writing separate hover rules for the icon works, until you add a focus state, a disabled state and a dark theme. currentColor removes that bookkeeping: the icon simply uses whatever text color its surroundings have.

Here is how it resolves, how to convert a mono icon to use it, and where it stops working.

What currentColor means

currentColor is a CSS keyword that stands for the value of the color property on the same element. On its own that sounds circular, but color is inherited. An SVG <path> nested in a <button> inherits the button's color, so fill="currentColor" on that path means "fill with the text color around me."

<a class="chain-link" href="/networks/base">
  <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true">
    <path fill="currentColor" d="M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z" />
  </svg>
  Base
</a>
.chain-link { color: #9aa0aa; }
.chain-link:hover { color: #ffffff; }

Hovering the link turns both the text and the icon white. There is no rule targeting the SVG at all.

If no ancestor sets a color, currentColor falls back to the default text color, which is usually black. You can also set color on the <svg> element itself when an icon should differ from its label.

fill written as an attribute, like fill="currentColor", is a presentation attribute. Any CSS rule that sets fill on the same element overrides it, however low its specificity. That is useful, since you can still override a single icon from your stylesheet, but it also explains surprises: a global rule like svg path { fill: #000 } in a reset or third-party stylesheet will beat every currentColor attribute on the page.

Converting a mono icon

A single-color icon file usually hard-codes its color:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
  <path fill="#FFFFFF" d="M12 3v6.65l5.63 2.52z" />
  <path fill="#FFFFFF" fill-rule="evenodd" d="m12 15.43-5.63-3.26L12 3l5.63 9.17z" />
</svg>

Replace each color value with currentColor:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
  <path fill="currentColor" d="M12 3v6.65l5.63 2.52z" />
  <path fill="currentColor" fill-rule="evenodd" d="m12 15.43-5.63-3.26L12 3l5.63 9.17z" />
</svg>

Leave fill="none" alone. It means "no fill" and is often set on the root so that only shapes with an explicit fill are painted.

Colors can hide in more places than the fill attribute, so search the file for all of these:

  • stroke="#..." on outlined shapes.
  • stop-color on gradient stops. A gradient from currentColor to currentColor with different stop-opacity values gives a soft single-hue gradient that still follows the text color.
  • style="fill:#fff" attributes, which beat presentation attributes.
  • A <style> block with class rules such as .cls-1{fill:#fff}, common in files exported from design tools.

If you copy a mono icon's code from Web3 Icons, every fill, stroke and stop color is set to the mono color you picked. A single find-and-replace of that hex value with currentColor converts the whole file.

Only do this for mono artwork. Replacing every color in a branded logo with currentColor flattens it into a silhouette.

Hover, focus, disabled and selected states

Because the icon reads color, every state you already style for text applies to it:

.tab {
  color: #8b9099;
  transition: color 120ms ease-out;
}
.tab:hover { color: #d7dae0; }
.tab[aria-selected="true"] { color: #ffffff; }
.tab:disabled { color: #4a4f58; }

The transition animates the icon as well, since its fill is derived from color frame by frame.

One caution about muted states. If an icon is the only thing that identifies a control, for example a network button that shows a chain logo and no name, keep it at 3:1 contrast or more against its background. That is the WCAG 1.4.11 threshold for graphics people need in order to understand or operate the interface. Logos themselves are exempt from that criterion, but a gray-on-gray icon nobody can make out still fails the user. The non-text contrast guide covers where the rule applies.

Dark mode without icon rules

Put your colors in custom properties, switch them per theme, and let currentColor carry them into the icons:

:root {
  --text: #1b1d21;
  --muted: #5f6570;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --text: #eef0f3;
    --muted: #9aa0aa;
  }
}

:root[data-theme="dark"] {
  --text: #eef0f3;
  --muted: #9aa0aa;
}

.token-row { color: var(--text); }
.token-row .network { color: var(--muted); }

Every mono icon inside .token-row now works in both themes, and so does a manual theme toggle that sets data-theme to light or dark. Full-color logos are a different problem, since they keep their own colors; the dark mode guide deals with those.

There is an accessibility bonus too. In Windows forced colors mode, the browser replaces text colors with the user's chosen system colors. Icons that use currentColor pick up the forced text color automatically, so they stay consistent with the user's chosen palette. It is still worth checking icons in that mode, since browsers differ in how they treat hard-coded SVG fills there.

Why img and CSS backgrounds ignore it

currentColor only works when the SVG is part of the page's document. Load the same file with <img src="icon.svg"> or background-image: url(icon.svg), and the browser renders it as a separate, isolated image. Your page's CSS doesn't reach inside, and color doesn't inherit across that boundary. Inside the file, currentColor resolves against the file's own default, which is usually black.

That leaves three ways to get the behavior with an external file:

Method How color reaches the icon
Inline <svg> Inherits color from its parent directly
<use href="/sprite.svg#eth"> The referenced shapes inherit color from the <use> element
CSS mask background-color: currentColor paints through the icon's shape

Sprites keep one copy of each shape while still following text color; the SVG sprites guide shows the setup. External sprite files must come from the same origin as the page. Masks work with any single-color file and are covered in the CSS mask guide.

Two tones from one color

currentColor is one value, but a mono icon sometimes has a secondary part, like the lighter facets of a coin. Two ways to keep both tied to the text color:

<path fill="currentColor" d="M12 3v6.65l5.63 2.52z" />
<path fill="currentColor" fill-opacity="0.6" d="m12 3-5.63 9.17L12 9.65z" />

Or give the secondary part a class and set it from your stylesheet, falling back to currentColor:

.icon .accent { fill: var(--icon-accent, currentColor); }

The first keeps the file self-contained. The second lets a theme choose a separate accent, which only works for inline SVG, because page styles can't reach into an <img>.

More in Using crypto logos

All guides