You Can Store Multiple Variants of an SVG in the Same File
I was working on a site that used an SVG company logo. The logo was used twice—once in the header, once in the footer. The header had a light background, so the logo was a dark variant, whereas in the footer, the background was dark so required the light variant.
Working with inline SVG solves this problem quite easily. The logo in the header can define a <symbol>
, which both the header and footer can inherit, and each can be styled with CSS
<!-- in the header -->
<svg width="120" height="120">
<symbol id="logo" viewBox="0 0 120 120">
<path d="..."/>
</symbol>
<use xlink:href="#logo" />
</svg>
<!-- in the footer -->
<svg width="120" height="120">
<use xlink:href="#logo" />
</svg>
header svg { fill: black; }
footer svg { fill: white; }
But here was the issue: the site was built with a CMS and the logos were <img>
elements. To address this, we simply had two SVG files, one called logo-light.svg
and the other logo-dark.svg
, but this seemed a bit redundant. What if it were possible to have one image, and a way to tell it which variant to use.
It is already possible to use adaptive SVGs that will change their content according to their dimensions, but in this case, there was nothing to differentiate the logos other than the background they were sitting on.
Here’s what I came up with:
The Target Hack
You only need one image, logo.svg
, but you can pass it the variant you want it to display by passing it in as a hash: logo.svg#light
or logo.svg#dark
. Here’s the code:
<!-- in logo.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
<style>
#light:target ~ path { fill: white; }
#dark:target ~ path { fill: black; }
</style>
<g id="light"></g>
<g id="dark"></g>
<path d="..."/>
</svg>
In the SVG are two empty <g>
elements, each with an ID to represent a variant the logo could have—in this case, either light or dark, but it could be extended to any number of variants. I make sure to put these at the top level of the SVG, before any other rendered content.
The key is the use of the :target
CSS pseudo-selector, which selects an element if its ID matches the hash portion of the URL. When calling logo.svg#light
, the first CSS rule, #light:target
is matched; for #dark
, it will be the second.
Much like the checkbox hack, this gives us enough leverage to style the rest of the SVG based on which element is being targeted. We use the general sibling selector, ~
, to select the <path>
element, and colour it either white or black, depending.
And there you have it: a way to re-style an embedded SVG by passing a variant ID into the URL.
The Filter Hack
As I was writing this, I came across an issue where I didn’t have access to the SVG being inserted, so the previous technique didn’t work; but there’s another technique involving SVG filters.
<svg height="0">
<filter id="light">
<feColorMatrix type="matrix" color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" />
</filter>
<filter id="dark">
<feColorMatrix type="matrix" color-interpolation-filters="sRGB" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
</filter>
</svg>
This SVG contains two colour filters: one that fills the target with white, preserving alpha; and another that fills it wil black. By using the following CSS, we can apply the SVG filter to an arbitrary HTML element, including our image.
header svg { filter: url(#light); }
footer svg { filter: url(#dark); }
One important aspect is the use of color-interpolation-filters="sRGB"
, which corrects the gamma so that the values in the matrix are simply the desired RGB values divided by 255. rgb(102, 51, 153)
has a colour matrix of 0 0 0 0 0.4 0 0 0 0 0.2 0 0 0 0 0.6 0 0 0 1 0
.
What makes this technique even more powerful is that there is a way it can be applied with just CSS and no markup, with the power of data URIs:
header svg { filter: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E %3Cfilter id='x'%3E %3CfeColorMatrix type='matrix' color-interpolation-filters='sRGB' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0' /%3E %3C/filter%3E%3C/svg%3E#x"); }
footer svg { filter: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E %3Cfilter id='x'%3E %3CfeColorMatrix type='matrix' color-interpolation-filters='sRGB' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0' /%3E %3C/filter%3E%3C/svg%3E#x"); }
The one final trick is using a hash on the end of the data URI that points to the <filter>
element.