Responsive svg colors using styled components and css variables in react.


September 3, 2021 / 15 min read

Last updated: October 19, 2023

SVG with responsive colors.

In this post we are going to look into one way to allow embedded svg images to follow theme colors. We will be doing this in nextjs/react, using styled components and css variables.

To start off, I created a sample SVG image using Inkscape, and saved it as a plain svg.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="33.916588mm" height="33.916588mm" viewBox="0 0 33.916588 33.916588" version="1.1" id="svg5" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs2" />
<g id="layer1" transform="translate(-67.532603,-100.81584)">
<circle style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="path850" cx="84.490898" cy="117.77413" r="15.958295" />
<circle style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.264583" id="path1000" cx="76.737106" cy="110.88526" r="2.1071048" />
<path style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="path1500" d="m 93.573987,123.14183 a 9.5406475,6.1189637 0 0 1 -9.215558,4.53526 9.5406475,6.1189637 0 0 1 -9.215558,-4.53526" />
<circle style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.264583" id="circle1830" cx="91.333298" cy="110.88526" r="2.1071048" />
</g>
</svg>

/public/any_svg.svg

We can embed the SVG directly using the following code.

import AnySvg from '/public/any_svg.svg'
<img src={AnySvg} alt='Emoticon.' />

Which results in the following image.


Emoticon.
Let's call him Albinie. Note that in de dark theme, Albinie is not very visible because of the lack of contrast between the black parts of the SVG and the dark background. You can switch the theme with the button in the top right. Let's try to make the black parts of the svg follow the theme color, making Albinie visible in both themes.

The first idea you might get is to override the fill and stroke properties, so that we can override all the colors in the SVG. This is not possible because we're having a very specific problem here: we want to make the black colored lines responsive to the theme, but the red eyes can stay the same color in both themes. Even if we wanted to change the eye color depending on the theme, we couldn't use the same variable, since we still want a difference in color between the eyes and the rest of Albinie.

Did you know that there's an online tool that converts an svg element to jsx compatible code? If we use this tool with standard settings to convert our svg to jsx, we get the following code.

import React from "react";
function Icon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="128.189" height="128.189" version="1.1" viewBox="0 0 33.917 33.917" >
<g fillRule="evenodd" transform="translate(-67.533 -100.816)">
<circle cx="84.491" cy="117.774" r="15.958" fill="none" stroke="#000" strokeDasharray="none" strokeMiterlimit="4" strokeOpacity="1" strokeWidth="2" ></circle>
<circle cx="76.737" cy="110.885" r="2.107" fill="red" strokeWidth="0.265" ></circle>
<path fill="none" stroke="#000" strokeDasharray="none" strokeLinecap="round" strokeMiterlimit="4" strokeOpacity="1" strokeWidth="2" d="M93.574 123.142a9.54 6.119 0 01-9.216 4.535 9.54 6.119 0 01-9.215-4.535" ></path>
<circle cx="91.333" cy="110.885" r="2.107" fill="red" strokeWidth="0.265" ></circle>
</g>
</svg>
);
}
export default Icon;

We can now use the svg as a react component.

import AnySvg from '/public/AnySvg'
<AnySvg />

Result:

Note that Albinie is not his original size any more. The image isn't scaled to the width of the blog, because the <img> tag isn't used here.

In order to automatically scale the svg, we can simply replace the width and height tags in the react component. Instead of defining a width in pixels, we set the width to 100% and remove the height tag altogether.

<svg xmlns="http://www.w3.org/2000/svg" width="100%" version="1.1" viewBox="0 0 33.917 33.917" >

Result:

We continue by creating a styled component from the Icon component (this is the emoticon component). Here we will use theme data in order to set the right colors in the svg. In my case the colors are set in css variables, so we copy a color into the stroke property.

import styled from 'styled-components'
const StyledIcon = styled(Icon)` stroke: var(--textColor); `

We also have to unpack the props from the styled component into the svg tag, so that the style property is correctly set.

function Icon(props) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="100%" version="1.1" viewBox="0 0 33.917 33.917" {...props} >
}

Albinie just got some white in his eyes... And is still not responsive to theme changes. This is clearly not what we wanted to achieve. Do not despair however, as there is only one step left, editing the stroke props in the svg.

By removing the right stroke props from the svg, we can control which curves get their stroke set via the styled component, and which ones don't. In our case we want to make the mouth, and the circle around the face, responsive to theme changes. They are both colored black, so we can do a search and replace in the file.

ctrl + f, then search for stroke="#000" and replace all of them with an empty string. This results in the following.


Try changing the theme now with the button in the top right corner! An albino smiley will be clearly visible no matter what theme is chosen. Check out the robotics post if you would like to see theme-responsive svg illustrations that are actually useful.