CSS Custom Properties Scoped to a Class, Not :root
When we rewrote the Twenty1 homepage this month, we had a problem. The rest of the site runs on a Tailwind-based token set with its own colors, spacing, and type scale. We wanted an entirely different visual language on the homepage: editorial typography, a warm off-white ground, a navy accent. Touching :root would have broken every interior page.
The fix was simpler than we expected. Instead of defining tokens globally, we scoped them to a class:
.studio-home {
--studio-paper: #f7f7f5;
--studio-ink: #152a3b;
--studio-blue: #0b5cab;
--studio-line: #d9dedf;
--studio-muted: #5c6870;
}
The homepage component applies studio-home to its outermost wrapper. Every section inside it inherits --studio-paper and --studio-blue. Every other route sees nothing. Zero collision.
The same pattern carries easing functions as tokens:
.studio-home, .studio-header, .studio-footer {
--studio-ease: cubic-bezier(.16,1,.3,1);
--studio-spring: linear(0, .104, .348, .62, .832, .958, 1.012, 1.024, 1.016, 1.006, 1);
}
Using the CSS linear() easing function (now supported in all modern browsers) to approximate a spring curve means any element inside those containers can write transition: transform .65s var(--studio-spring) and get a bouncy, physically motivated feel. Same token, consistent motion across header, footer, and body.
Why this matters more than it first appears. CSS custom properties inherit through the DOM. Define them on .studio-home and they flow down to every child without any prop drilling, context API, or Tailwind variant. Components inside that wrapper don't even need to know they're in a special context. They just reference var(--studio-ink) and the right color shows up.
The naming also acts as documentation. A developer looking at an unfamiliar component sees --studio-blue and immediately knows it belongs to the editorial system. --accent or --primary would give no such signal.
The other benefit: progressive replacement. Because the new system is fully additive (no :root variables were removed or overridden), we could ship the new homepage to production while the blog, service pages, and contact form continued running on the old token set. We didn't need a big-bang cutover.
The pattern scales. If we want to give a landing page its own distinct look later, we add a new top-level class with its own token set. Each page opts in explicitly. The global stylesheet stays clean.
The actual page shrank from 633 lines to 88. Most of that reduction came from removing animated components we'd accumulated over months. But the clean token scope made it possible to do that surgery without fear: we knew exactly which variables each component would see, because the class boundary made it explicit.
If you're redesigning a section of a site without touching everything else, don't reach for CSS Modules or a new CSS layer first. Try class-scoped custom properties. One class on the wrapper, all your tokens inside it, and every other route stays untouched.