TIL: Using SVGs for Light/Dark Mode
Published Tue 15 August 2023 by Daniel Playle
When using images, I prefer SVGs since they don't suffer from quality issues when manipulated (unlike JPEGs). The other attractive quality is that SVGs provide some hope of being able to integrate well into a webpage, replicating the style of the page (which you can't do with a static image). This is especially problematic when supporting both light/dark modes and throwing in the possibility of changing the style of the page at a later time (you don't want to have to go back and create new images just to change the style).
However, despite the promise of SVGs, they come with some pitfalls, and that is what this TIL is about:
Generating SVGs
Generating SVGs is a bit of a pain because they're not widely supported, but there are still many tools that do. I found gnuplot
to be a great tool for generating these. That said, the SVGs it creates will have colours that are well-defined so that the SVG can be used as-is. If we want it to be dynamic (to some degree), then this will need to change.
In my case, the SVGs I'm working with are monochromatic, so I can simply switch out colours to singular values of whatever I want. On this site, the foreground colour is defined in CSS as var(--fg-color)
, so that'll do, and practically speaking it's just a matter of using sed
. For instance, replacing all #FF00FF
values:
sed 's#rgb(\s*255\s*,\s*0\s*,\s*255\s*)#var(--fg-color)#g' <image.svg
You can do this for other aspects as well, such as fonts.
Embedded vs Inlined SVGs
Embedded SVGs, such as those included with a <img>
tag are considered separate documents and therefore do not inherit or otherwise have access to the main document's style. Therefore if you try to include the SVG you constructed above using an <img>
tag, you're going to be disappointed to find the style doesn't work as you might have hoped.
What we can do instead is just embed the SVG image (literally in the form of the SVG's tags) into the main document. This has some drawbacks such as inflating the size of our document, and worse: in such a way that your image isn't going to be cached, so this isn't great if your SVG is large. But work it will!
The Wildcard: Masking SVGs
We can actually embed the SVG by using a CSS mask, this is where we set a background on an object and allow parts of that background to come through based on some other image (in this case our SVG). This works because the mask can be defined in terms of the main document's style, but it has some downfalls: namely the lack of access to fonts. This means that if you have a custom font (or more extreme: a font you use a CSS variable for), then you're out of luck. Additionally, whereas when you inline the SVG users can interact with the SVG (such as highlighting text), that's not possible in this approach.
An example of this in action is:
<div style="background-color: var(--fg-color); -webkit-mask-image: url(image.svg); mask-image: url(image.svg); width: 800px; height: 300px;"></div>
How This Looks
Looking at these three options side-by-side, you can see there are material differences (ironically this is shown in PNG format):
The first (embedded) example maybe isn't fair, because you could tweak the style of the SVG from within the SVG, but I want to stress the point that the SVG can't use the main document's style when used like this.