Animating SVG Gradient Background Colors With CSS Custom Properties

Changing SVG colors is easy right?

I certainly thought it should be and in the past it has not been too difficult, with things like changing the stroke or fill of the SVG on hover, but what about when we want to hover a solid colored SVG icon and have it change to a gradient? This proved to be much more difficult and led me down several different paths trying to get it to work.

Table of Contents

Setting stroke and fill on hover

My first thought was to treat the gradient like any other color and try to set stroke and fill to the gradient on hover. This did not seem to be a thing that was possible, however, so I needed to keep looking.

Using <linearGradient> in the SVG code

After the naive approach did not work, I did some research and found a Stack Overflow Post that recommended using <linearGradient> directly in the SVG code and applying it as the stroke and fill on hover. Note: this example uses Tailwind pretty heavily.

linear-gradient-svg.html

<button
type="button"
class="group relative rounded-full focus:outline-none"
aria-label="Fast-forward 10 seconds"
>
<svg
class="h-10 w-10"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
class="group-hover:stroke-[url(#paint0_linear_201_22852)] dark:group-hover:stroke-[url(#paint0_linear_201_20128)]"
d="M9 10L9 15"
stroke="#9DA3AF"
stroke-width="1.5"
stroke-linecap="square"
/>
<path
class="group-hover:stroke-[url(#paint1_linear_201_22852)] dark:group-hover:stroke-[url(#paint1_linear_201_20128)]"
d="M13.75 10H13.25C12.5596 10 12 10.5596 12 11.25V13.75C12 14.4404 12.5596 15 13.25 15H13.75C14.4404 15 15 14.4404 15 13.75V11.25C15 10.5596 14.4404 10 13.75 10Z"
stroke="#9DA3AF"
d="M3.25 12C3.25 7.16751 7.16751 3.25 12 3.25C14.2529 3.25 16.3063 4.10071 17.8578 5.5H15.5V7H20.25V2.25H18.75V4.28624C16.9474 2.70773 14.5852 1.75 12 1.75C6.33908 1.75 1.75 6.33908 1.75 12C1.75 17.6609 6.33908 22.25 12 22.25C17.6609 22.25 22.25 17.6609 22.25 12C22.25 10.7394 22.0221 9.5305 21.6047 8.41329L20.2105 8.96769C20.5593 9.91159 20.75 10.9328 20.75 12C20.75 16.8325 16.8325 20.75 12 20.75C7.16751 20.75 3.25 16.8325 3.25 12Z"

Example

Hover me:

This ultimately did work, but it seemed to be impossible to use transitions to have the colors gradually change, which led to a less than desirable jarring color swap.

Using @property for the gradient colors

After almost deciding to settle and just accept that I would not be able to fade the colors in and out nicely, I remembered mask-image and, after some searching, stumbled upon this post which inspired the solution utilizing CSS custom properties.

gradient-icon.css
forward-button.html
forward-icon.svg

@property --gradient-start {
syntax: '<color>';
inherits: false;
initial-value: oklch(67.65% 0.043 285.21);
}
@property --gradient-stop {
syntax: '<color>';
inherits: false;
initial-value: oklch(67.65% 0.043 285.21);
}
@media (prefers-color-scheme: dark) {
@property --gradient-start {
syntax: '<color>';
inherits: false;
initial-value: oklch(71.44% 0.019 264.45);
}
@property --gradient-stop {
syntax: '<color>';
inherits: false;
initial-value: oklch(71.44% 0.019 264.45);
}
--gradient-stop: oklch(67.59% 0.18851878808119876 292.4954219580293);

Example

Hover me:

We now have a nice fading in/out transition from a solid color to a gradient. This is accomplished using @property and setting our initial start/stop colors for the gradient to a solid color, and then swapping out those variables for new start/stop colors on hover.

Wrapping up

I was surprised to learn there was not an easier solution built into CSS, but perhaps there will be one day. It seems like this is a lot of work to accomplish what feels like it should be simpler. Is there a better way to do this that I didn't try? Hit me up on Twitter and let me know! @RobbieTheWagner.