I recently explained how I use it , and CSS Media Queries to develop what I call adaptive SVGs. Symbols allow us to define an element once usage it again and again, making SVG animations easier to maintain, more efficient, and lighter.
Since I wrote that explanation, I’ve designed and implemented new ones Beautiful 7 animated images about my website. They play on the pioneering theme for web design, featuring seven beautiful characters from the Old West.
And lets me define a character design and reuse it across multiple SVGs and pages. First I created my characters and put them all in one in a hidden library SVG:
Then I referenced those symbols in two other SVGs, one for large and the other for small screens:
Elegant. But then came the annoying thing. I could reuse the characters, but couldn’t animate or style them. I added CSS rules that target elements within the symbols referenced by a but nothing happened. Colors stayed the same and things that needed to move remained static. It felt like I was running into an invisible barrier, and I was.
Understanding the Shadow DOM Barrier
When you refer to the contents of a symbol of usea browser makes a copy of it in the Shadow DOM. Each instance becomes its own encapsulated copy of the reference meaning that outside CSS cannot break the barrier of formatting elements directly. For example, this is the case in normal circumstances tapping value triggers a CSS animation:
.tapping {
animation: tapping 1s ease-in-out infinite;
}
But when the same animation is applied to a copy of the same foot, nothing happens:
.tapping {
animation: tapping 1s ease-in-out infinite;
}
That’s because the within the element is in a protected shade tree and the CSS Cascade stops dead at the border. This behavior can be frustrating, but it is intentional because it ensures that reused symbol content remains consistent and predictable.
As I learned how to develop adaptive SVGs, I discovered all kinds of attempts to circumvent this behavior, but most of them sacrificed the reusability that makes SVG so elegant. I didn’t want to duplicate my characters just to have them blink at different times. I wanted a single with instances having their own timings and expressions.

Custom CSS properties come to the rescue
While working on my pioneer animations, I learned that regular CSS values ​​cannot cross the border into the Shadow DOM, but CSS Custom Properties can. And even though you can combine elements within 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 makes those styles available to elements within the style. referred to.
I added rotate to an inline style applied to the contents:
Then you defined the foot tap 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 set a symbol to use custom CSS properties, I can pass as many values ​​as I want predisposition. For example, I could define variables for fill, opacityor transform. What is 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 custom CSS properties in this way is solid, and every modern browser handles this behavior correctly. I’ll show you a few ways I’ve used this technique, starting with a multi-colored icon system.
A multi-colored icon system
When I need to maintain a set of icons, I can save an icon once in a and then use custom properties to apply colors and effects. Instead of having to duplicate SVGs for each theme, each use can carry its own values.

For example, I have one --icon-fill custom property for the standard fill color of the in this Bluesky icon:
Then when I need to vary what that icon looks like, for example in a
— I can pass again fill color values ​​for each instance:
These icons have the same shape, but look different thanks to their inline styles.
Data visualizations with custom CSS properties
We can use And in much more practical ways. They’re also useful for creating lightweight data visualizations, so imagine an infographic about three well-known ones Wild West sheriffs: Wyatt Earp, Pat GarrettAnd Bat Masterson.

Each sheriff’s profile uses the same set of three SVG symbols: one for a bar showing the length of a sheriff’s career, another for the number of arrests made, and another for the number of homicides. Pass custom property values ​​to each instance can vary bar length, arrest scale, and kill color without duplicating SVGs. I first created symbols for those items:
Each symbol accepts one or more values:
--career-lengthfits thewidthof the career bar.--career-colourchanges thefillcolor of that bar.--arrest-scalechecks the size of the arrest badge.--kill-colourdefines thefillkill icon color.
I can use this to develop a profile of each sheriff using elements with different inline styles, starting with Wyatt Earp.
Each shares the same symbol elements, but the inline variables change their colors and sizes. I can even animate those values ​​to emphasize 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;
}
Custom CSS properties aren’t just useful for styling; they can also channel data between HTML and SVG’s inner geometry, linking visual attributes such as color, length, and scale to semantics such as arrest numbers, career length, and homicides.
Environmental animations
I started learning to animate elements within symbols while creating the animated images for my website’s Magnificent 7. To reduce complexity and make my code lighter and more maintainable, I had to define each character once and reuse it in SVGs:

But I didn’t want those characters to remain static; I needed subtle movements that would bring them to life. I wanted their eyes to blink, their feet to tap, and their whiskers to twitch. To animate these details, I pass animation data to elements within those symbols using custom CSS properties, starting with the blinking.
I implemented the blink effect by placing an SVG group over the bandits’ eyes and then changing it opacity.

To make this possible, I added an inline style with a custom CSS 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 each 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 one --blink-delay before they all start flashing, by passing one more custom property:
use[data-outlaw="1"] { --blink-delay: 1s; }
use[data-outlaw="2"] { --blink-delay: 2s; }
use[data-outlaw="7"] { --blink-delay: 3s; }

Some characters tap their feet, so I added an inline style with a custom CSS property to those groups as well:
To define 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 character 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 move via an inline style with a custom CSS property describing how his mustache transforms:
To define the shaking 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 character 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;
}
These moving parts bring the characters to life, but my formatting remains remarkably sparse. By combining several animations into a single statement, 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 solutions
Even though this technique seems bulletproof, there are some pitfalls to avoid:
- Custom CSS properties only work if they are referenced with a
var()within one. Forget that, and you’ll wonder why nothing is being updated. Also traits that are not naturally inherited, such asfillortransformshould usevar()in their value to benefit from the cascade. - It’s always best to include a fallback value next to a custom propertylike
opacity: var(--eyelids-opacity, 1);to ensure that SVG elements display correctly even without custom property values ​​applied. - Inline styles set via the
styleattribute takes precedenceso if you combine inline and external CSS, remember that Custom Properties follow the normal cascading rules. - You can always use DevTools to inspect custom property values. Select one
instance and check the Computed Styles panel to see which custom properties are active.
Conclusion
The And elements are among the most elegant but sometimes frustrating aspects of SVG. The Shadow DOM barrier makes animating it more difficult, but Custom CSS properties act as a bridge. They allow your color, movement and personality to transcend that invisible line, resulting in cleaner, lighter and, best of all, fun animations.
(gg, yk)
#Beautiful #SVGs #custom #CSS #properties #Smashing #Magazine


