I explained recently how I 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. and let me define a character design and reuse it across multiple SVGs and pages. First, I created my characters and put each into a inside a hidden library SVG:http://www.w3.org/2000/svg" style="display:none;"> [...] [...] [...] Then, I referenced those symbols in two other SVGs, one for large and the other for small screens: 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 , 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 BarrierWhen you reference the contents of a symbol with use, a browser creates a copy of it in the Shadow DOM. Each instance becomes its own encapsulated copy of the referenced , 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: .tapping { animation: tapping 1s ease-in-out infinite;}But when the same animation is applied to a instance of that same foot, nothing happens: .tapping { animation: tapping 1s ease-in-out infinite;}That’s because the inside the element is in a protected shadow tree, and the CSS Cascade stops dead at the 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 with instances that have their own timings and expressions.CSS Custom Properties To The RescueWhile 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 , 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 being referenced. I added rotate to an inline style applied to the content: 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 SymbolOnce I’ve set up a symbol to use CSS Custom Properties, I can pass as many values as I want to any instance. For example, I might define variables for fill, opacity, or transform. What’s elegant is that each instance can then have its own set of values. 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 SystemWhen I need to maintain a set of icons, I can define an icon once inside a 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.For example, I applied an --icon-fill custom property for the default fill colour of the in this Bluesky icon : Then, whenever I need to vary how that icon looks — for example, in a and — I can pass new fill colour values to each instance: These icons are the same shape but look different thanks to their inline styles.Data Visualisations With CSS Custom PropertiesWe can use and in plenty more practical ways. They’re also helpful for creating lightweight data visualisations, so imagine an infographic about three famous Wild West sheriffs: Wyatt Earp, Pat Garrett, and Bat Masterson.Each sheriff’s profile uses the same set of SVG three symbols: one for a bar representing the length of a sheriff’s career, another to represent the number of arrests made, and one more for the number of kills. Passing custom property values to each instance can vary the bar lengths, arrests scale, and kills colour without duplicating SVGs. I first created symbols for those items: Each symbol accepts one or more values:--career-length adjusts the width of the career bar.--career-colour changes the fill colour of that bar.--arrest-scale controls the arrest badge size.--kill-colour defines the fill colour of the kill icon.I can use these to develop a profile of each sheriff using elements with different inline styles, starting with Wyatt Earp.http://www.w3.org/2000/svg"> Each shares the same symbol elements, but the inline variables change their colours and sizes. I can even animate those values to highlight their differences:@keyframes pulse { 0%, 100% { --arrest-scale: 1; } 50% { --arrest-scale: 1.2; }}use[href="#arrests-badge"]:hover { animation: pulse 1s ease-in-out infinite;}CSS Custom Properties aren’t only helpful for styling; they can also channel data between HTML and SVG’s inner geometry, binding visual attributes like colour, length, and scale to semantics like arrest numbers, career length, and kills.Ambient AnimationsI started learning to animate elements within symbols while creating the animated graphics for my website’s Magnificent 7. To reduce complexity and make my code lighter and more maintainable, I needed to define each character once and reuse it across SVGs:http://www.w3.org/2000/svg" style="display:none;"> […] http://www.w3.org/2000/svg" id="svg-large"> http://www.w3.org/2000/svg" id="svg-small"> But I didn’t want those characters to stay static; I needed subtle movements that would bring them to life. I wanted their eyes to blink, their feet to tap, and their moustache whiskers to twitch. So, to animate these details, I pass animation data to elements inside those symbols using CSS Custom Properties, starting with the blinking. I implemented the blinking effect by placing an SVG group over the outlaws’ eyes and then changing its opacity. To make this possible, I added an inline style with a CSS Custom Property to the group: Then, I defined the blinking animation by changing --eyelids-opacity:@keyframes blink { 0%, 92% { --eyelids-opacity: 0; } 93%, 94% { --eyelids-opacity: 1; } 95%, 97% { --eyelids-opacity: 0.1; } 98%, 100% { --eyelids-opacity: 0; }}…and applied it to every character:use[data-outlaw] { --blink-duration: 4s; --eyelids-opacity: 1; animation: blink var(--blink-duration) infinite var(--blink-delay);}…so that each character wouldn’t blink at the same time, I set a different --blink-delay before they all start blinking, by passing another Custom Property:use[data-outlaw="1"] { --blink-delay: 1s; }use[data-outlaw="2"] { --blink-delay: 2s; }use[data-outlaw="7"] { --blink-delay: 3s; }Some of the characters tap their feet, so I added an inline style with a CSS Custom Property to those groups, too: Defining the foot-tapping animation:@keyframes tapping { 0%, 60%, 100% { --foot-rotate: 0deg; } 20% { --foot-rotate: -5deg; } 40% { --foot-rotate: 2deg; }}And adding those extra Custom Properties to the characters’ declaration:use[data-outlaw] { --blink-duration: 4s; --eyelids-opacity: 1; --foot-rotate: 0deg; animation: blink var(--blink-duration) infinite var(--blink-delay), tapping 1s ease-in-out infinite;}…before finally making the character’s whiskers jiggle via an inline style with a CSS Custom Property which describes how his moustache transforms: Defining the jiggle animation:@keyframes jiggle { 0%, 100% { --jiggle-x: 0px; } 20% { --jiggle-x: -3px; } 40% { --jiggle-x: 2px; } 60% { --jiggle-x: -1px; } 80% { --jiggle-x: 4px; }}And adding those properties to the characters’ declaration:use[data-outlaw] { --blink-duration: 4s; --eyelids-opacity: 1; --foot-rotate: 0deg; --jiggle-x: 0px; animation: blink var(--blink-duration) infinite var(--blink-delay), jiggle 1s ease-in-out infinite, tapping 1s ease-in-out infinite;}With these moving parts, the characters come to life, but my markup remains remarkably lean. By combining several animations into a single declaration, I can choreograph their movements without adding more elements to my SVG. Every outlaw shares the same base , and their individuality comes entirely from CSS Custom Properties.Pitfalls And SolutionsEven though this technique might seem bulletproof, there are a few traps it’s best to avoid:CSS Custom Properties only work if they’re referenced with a var() inside a . Forget that, and you’ll wonder why nothing updates. Also, properties that aren’t naturally inherited, like fill or transform, need to use var() in their value to benefit from the cascade.It’s always best to include a fallback value alongside a custom property, like opacity: var(--eyelids-opacity, 1); to ensure SVG elements render correctly even without custom property values applied.Inline styles set via the style attribute take precedence, so if you mix inline and external CSS, remember that Custom Properties follow normal cascade rules.You can always use DevTools to inspect custom property values. Select a instance and check the Computed Styles panel to see which custom properties are active.ConclusionThe and elements are among the most elegant but sometimes frustrating aspects of SVG. The Shadow DOM barrier makes animating them trickier, but CSS Custom Properties act as a bridge. They let you pass colour, motion, and personality across that invisible boundary, resulting in cleaner, lighter, and, best of all, fun animations.