Gradient fills for SVG icons

Florens Verschelde

In my 2016 article How to work with SVG icons, I concluded the “Advanced” section with this warning: sorry, gradient fills won’t work.

I was mostly referring to code like fill: linear-gradient(red, blue) which does not work because fill is from SVG which has its own gradient system, and linear-gradient is from CSS and made mostly for backgrounds. The two don’t mix well.

I knew we could probably use SVG’s <linearGradient/> element but hadn’t really tried it. Then a few months later I did try it, and it worked!, but I forgot to blog about it. So, how can we do this thing?

Putting gradients in your HTML

The most reliable technique I found was to add a SVG element in your HTML page (at the start or end of the <body>, for instance). It should define a <linearGradient/>:

<svg style="width:0;height:0;position:absolute;" aria-hidden="true" focusable="false">
  <linearGradient id="my-cool-gradient" x2="1" y2="1">
    <stop offset="0%" stop-color="#447799" />
    <stop offset="50%" stop-color="#224488" />
    <stop offset="100%" stop-color="#112266" />
  </linearGradient>
</svg>

This element should not be hidden with display:none, because some browsers will just ignore the gradient if we do that. Making it zero pixels high seems to work.

You can then use this gradient on your SVG icon:

<svg class="icon" fill="url(#my-cool-gradient) #447799;" aria-hidden="true" focusable="false">
  <use xlink:href="#symbol-id"></use>
</svg>

Or in your CSS:

.icon {
  /* gradient and fallback color */
  fill: url(#my-cool-gradient) #447799;
}
These two icons should be using a turquoise-to-dark-blue gradient.
Credits: Leaf by Gabriele Malaspina and Carpet by Ben Davis

Note that you cannot customize the gradient on a per-icon basis. If you want different gradients, you will need to create different SVG gradient definitions in you HTML.

Changing gradient colors with CSS variables

If we want to set our gradient colors from CSS, we could do it using CSS variables. We’re going to write our gradient definitions using CSS custom properties (var(--my-custom-property)).

<svg aria-hidden="true" focusable="false" style="width:0;height:0;position:absolute;">
  <linearGradient id="gradient-horizontal">
    <stop offset="0%" stop-color="var(--color-stop-1)" />
    <stop offset="50%" stop-color="var(--color-stop-2)" />
    <stop offset="100%" stop-color="var(--color-stop-3)" />
  </linearGradient>
  <linearGradient id="gradient-vertical" x2="0" y2="1">
    <stop offset="0%" stop-color="var(--color-stop-1)" />
    <stop offset="50%" stop-color="var(--color-stop-2)" />
    <stop offset="100%" stop-color="var(--color-stop-3)" />
  </linearGradient>
</svg>

Now we can set — and, if need be, change — those colors in our CSS:

#gradient-horizontal {
  --color-stop-1: #a770ef;
  --color-stop-2: #cf8bf3;
  --color-stop-3: #fdb99b;
}
#gradient-vertical {
  --color-stop-1: #00c3ff;
  --color-stop-2: #77e190;
  --color-stop-3: #ffff1c;
}

And, finally, use them as icon fills:

.icon-hgradient {
  fill: url(#gradient-horizontal) gray;
  /* We could use it as a stroke fill too:
  stroke: url(#gradient-horizontal) gray; */
}
.icon-vgradient {
  fill: url(#gradient-vertical) gray;
}

Here’s the result (in browser supporting CSS Custom Properties):

Testing icons with SVG gradient fills and CSS variables
The gradient fill is painted for each path of the icon. To avoid color mismatches (like the junction between the leaf and the stem), it might be useful to merge all or most paths in the icon’s source SVG.

Using gradients in external files

This technique works in Firefox, and used to work in Edge (confirmed in Edge 14 and 15, regressed in Edge 16 and Insider Preview). In the following test:

  1. Both icons are fetched from an external sprite: sprite.svg.
  2. The first icon uses a gradient from this HTML page, and the second one from sprite.svg.
.icon-sprite-gradient {
  fill: url(sprite.svg#my-warm-gradient) red;
}
Non-supporting browsers (Chrome, Safari, latest Edge…) should show a red fallback fill for the second icon.

Using gradients in CSS as data URLs

What if I told you that you could define your gradients in SVG, but put the SVG in your CSS as data URLs? Okay now I’m just being silly. But it works! At least it works in Firefox :P

/* Notice the  `id="grad"` in the SVG URL and how we’re using it at the end. Note that the `#` in hexadecimal colors must be escaped as `%23`. */
.icon-gradient-url {
  fill: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><linearGradient id='grad'><stop offset='0%' stop-color='%23ff00cc'/><stop offset='100%' stop-color='%23333399'/></linearGradient></svg>#grad") purple;
}

/* Same SVG, in base64 */
.icon-gradient-base64 {
  fill: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPjxsaW5lYXJHcmFkaWVudCBpZD0nZ3JhZCc+PHN0b3Agb2Zmc2V0PScwJScgc3RvcC1jb2xvcj0nI2ZmMDBjYycvPjxzdG9wIG9mZnNldD0nMTAwJScgc3RvcC1jb2xvcj0nIzMzMzM5OScvPjwvbGluZWFyR3JhZGllbnQ+PC9zdmc+#grad") purple;
}

See how we reference the gradient’s id with #grad at the end of the URL? Only Firefox seems to understand this syntax. Eh, too bad.

Trying to show a magenta to purple gradient. Non-supporting browsers (Chrome, Safari, Edge…) should show a purple fallback fill.

Let’s recap

  1. Yes you can have gradient fills!
  2. But you need to include SVG gradients in your HTML
  3. Colors can be hardcoded in the SVG gradient (in HTML), or set in CSS (using CSS variables)
  4. All icons using a given gradient will use the same colors, you can’t override on a specific color like this:
.icon-gradient-override {
  /* This works, as shown before */
  fill: url(#gradient-horizontal) gray;
  /* But this will do nothing… */
  --color-stop-1: white;
  --color-stop-2: gray;
  --color-stop-3: black;
}

If you need a lot of different gradient fills, this technique might not work for you. Creating 10 or 20 different SVG gradients in your HTML is not practical. But for simpler use cases, this should work in all current browsers (including IE11 if we avoid CSS variables).