Smashing Animations Part 6: Magnificent SVGs With <use> And CSS Custom Properties
SVG is one of those web technologies that’s both elegant and, at times, infuriating. In this article, pioneering author and web designer Andy Clarke explains his technique for animating SVG elements that are hidden in the Shadow DOM.
- Andy Clarke
- Nov 7, 2025
- 0 comments
Smashing Animations Part 6: Magnificent SVGs With <use> And CSS Custom Properties
- 10 min read
- SVG,
Animation,
Design,
CSS
About The Author
Often referred to as one of the pioneers of web design, Andy Clarke has been instrumental in pushing the boundaries of web design and is known for his creative …
More about
Andy ↬
*Weekly tips on front-end & UX.
Trusted by 182,000+ folks.*
SVG is one of those web technologies that’s both elegant and, at times, infuriating. In this article, pioneering author and web designer Andy Clarke explains his technique for animating SVG elements that are hidden in the Shadow DOM.
I explained recently how I use <symbol>, <use>, and CSS Media Queries to develop what I call adaptive SVGs. Symbols let us define an element once and then use it again and again, making SVG animations easier to maintain, more efficient, and lightweight.
Since I wrote that explanation, I’ve designed and implemented new Magnificent 7 animated graphics across my website. They play on the web design pioneer theme, featuring seven magnificent Old West characters.
[Graphics featuring seven magnificent Old West characters]
View this animated SVG on my website. (Large preview)
<symbol> and <use> let me define a character design and reuse it across multiple SVGs and pages. First, I created my characters and put each into a <symbol> inside a hidden library SVG:
<!-- Symbols library -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="outlaw-1">[...]</symbol>
<symbol id="outlaw-2">[...]</symbol>
<symbol id="outlaw-3">[...]</symbol>
<!-- etc. -->
</svg>
Then, I referenced those symbols in two other SVGs, one for large and the other for small screens:
<!-- Large screens -->
<svg xmlns="http://www.w3.org/2000/svg" id="svg-large">
<use href="outlaw-1" />
<!-- ... -->
</svg>
<!-- Small screens -->
<svg xmlns="http://www.w3.org/2000/svg" id="svg-small">
<use href="outlaw-1" />
<!-- ... -->
</svg>
Elegant. But then came the infuriating. I could reuse the characters, but couldn’t animate or style them. I added CSS rules targeting elements within the symbols referenced by a <use>, but nothing happened. Colours stayed the same, and things that should move stayed static. It felt like I’d run into an invisible barrier, and I had.
Understanding The Shadow DOM Barrier
When you reference the contents of a symbol with use, a browser creates a copy of it in the Shadow DOM. Each <use> instance becomes its own encapsulated copy of the referenced <symbol>, meaning that CSS from outside can’t break through the barrier to style any elements directly. For example, in normal circumstances, this tapping value triggers a CSS animation:
<g class="outlaw-1-foot tapping">
<!-- ... -->
</g>
.tapping {
animation: tapping 1s ease-in-out infinite;
But when the same animation is applied to a <use> instance of that same foot, nothing happens:
<symbol id="outlaw-1">
<g class="outlaw-1-foot"><!-- ... --></g>
</symbol>
<use href="#outlaw-1" class="tapping" />
.tapping {
animation: tapping 1s ease-in-out infinite;
That’s because the <g> inside the <symbol> element is in a protected shadow tree, and the CSS Cascade stops dead at the <use> boundary. This behaviour can be frustrating, but it’s intentional as it ensures that reused symbol content stays consistent and predictable.
While learning how to develop adaptive SVGs, I found all kinds of attempts to work around this behaviour, but most of them sacrificed the reusability that makes SVG so elegant. I didn’t want to duplicate my characters just to make them blink at different times. I wanted a single <symbol> with instances that have their own timings and expressions.
[Several animated elements within a single SVG symbol]
Several animated elements within a single SVG symbol. (Large preview)
CSS Custom Properties To The Rescue
While working on my pioneer animations, I learned that regular CSS values can’t cross the boundary into the Shadow DOM, but CSS Custom Properties can. And even though you can’t directly style elements inside a <symbol>, you can pass custom property values to them. So, when you insert custom properties into an inline style, a browser looks at the cascade, and those styles become available to elements inside the <symbol> being referenced.
I added rotate to an inline style applied to the <symbol> content:
<symbol id="outlaw-1">
<g class="outlaw-1-foot" style="
transform-origin: bottom right;
transform-box: fill-box;
transform: rotate(var(--foot-rotate));">
<!-- ... -->
</g>
</symbol>
Then, defined the foot tapping animation and applied it to the element:
@keyframes tapping {
0%, 60%, 100% { --foot-rotate: 0deg; }
20% { --foot-rotate: -5deg; }
40% { --foot-rotate: 2deg; }
use[data-outlaw="1"] {
--foot-rotate: 0deg;
animation: tapping 1s ease-in-out infinite;
Passing Multiple Values To A Symbol
Once I’ve set up a symbol to use CSS Custom Properties, I can pass as many values as I want to any <use> instance. For example, I might define variables for fill, opacity, or transform. What’s elegant is that each <symbol> instance can then have its own set of values.
<g class="eyelids" style="
fill: var(--eyelids-colour, #f7bea1);
opacity: var(--eyelids-opacity, 1);
transform: var(--eyelids-scale, 0);"
<!-- etc. -->
</g>
use[data-outlaw="1"] {
--eyelids-colour: #f7bea1;
--eyelids-opacity: 1;
use[data-outlaw="2"] {
--eyelids-colour: #ba7e5e;
--eyelids-opacity: 0;
Support for passing CSS Custom Properties like this is solid, and every contemporary browser handles this behaviour correctly. Let me show you a few ways I’ve been using this technique, starting with a multi-coloured icon system.
A Multi-Coloured Icon System
When I need to maintain a set of icons, I can define an icon once inside a <symbol> and then use custom properties to apply colours and effects. Instead of needing to duplicate SVGs for every theme, each use can carry its own values.
[Custom properties for the fill colours in several Bluesky icons]
Custom properties for the fill colours in several Bluesky icons. (Large preview)
For example, I applied an --icon-fill custom property for the default fill colour of the <path> in this Bluesky icon :
<symbol id="icon-bluesky">
<path fill="var(--icon-fill, currentColor)" d="..." />
</symbol>
Then, whenever I need to vary how that icon looks — for example, in a <header> and <footer> — I can pass new fill colour values to each instance:
<header>
<svg xmlns="http://www.w3.org/2000/svg">
<use href="#icon-bluesky" style="--icon-fill: #2d373b;" />
</svg>
</header>
<footer>
<svg xmlns="http://www.w3.org/2000/svg">
<use href="#icon-bluesky" style="--icon-fill: #590d1a;" />
</svg>
</footer>
These icons are the same shape but look different thanks to their inline styles.
Data Visualisations With CSS Custom Properties
[...]