
Thoughts on CSS variables for maintainable component systems
How CSS custom properties scale from single sites to multisites, and what happens when you try to scale them too far.
I spent three weeks last year reworking the colour token layer in a multisite platform’s component library. The original system worked — developers could write components against a set of CSS variables and change site-wide colours from one file. Two years later, the file had grown to three hundred lines. Sites had locally overridden eighty percent of it. The system that was supposed to prevent drift became the place where drift accumulated.
The rebuild started simpler. I think CSS variables are genuinely powerful for component systems. I also think there is a scale where they stop being tools and start being problems, and knowing where that boundary lives is the difference between a system that compounds and one that slowly collapses under its own configuration.
What CSS variables do well
A component system built on CSS variables has a real advantage: a component does not need to know about colours, spacing, or typography. It references tokens. A button uses var(--button-background) instead of var(--color-primary) or a hardcoded hex. When the design system changes the button’s background, every button changes. No rebuild. No redeployment.
.button { background-color: var(--button-background); color: var(--button-foreground); padding: var(--button-padding); border-radius: var(--button-radius); font-weight: var(--button-font-weight);}
.button:hover { background-color: var(--button-background-hover); color: var(--button-foreground-hover);}This works in a single-site context. Every page gets the same token set. A product team that wants to adjust button styling changes the tokens and the change propagates. Predictable. Clean. Maintainable by someone who is not a developer.
The same system holds in multisite contexts up to a point — when drift is the exception, not the rule. A medical school might want slightly different button styling than the business school, but both are using the same underlying components, and the differences fit in configuration.
Where it fractures
The problem arrives when institutions want to override individual tokens locally. A site sets --button-radius: 0. Another sets --button-font-weight: 700. Another changes four tokens at once because a new brand guideline arrived. A year later, every site has overridden something. The shared token set is now a suggestion.
This is not a failure of CSS variables. It is a failure of architecture. CSS variables are gloriously flexible — you can set them globally, at the component level, on a specific instance. That flexibility becomes a problem when the expectation is that shared tokens will stay shared.
I watched a team solve this by treating tokens as layers. The base layer was canonical — colours that could not be overridden. The component layer was where sites could adjust if a component genuinely needed to look different. The instance layer was for one-offs. Every layer had a cost written in a comment next to it: “Overriding this breaks multisite coherence.”
It worked less because of the system and more because the team understood the implication. Once the team changed, the discipline faded. New developers did not know why the cost was so high. They just knew that setting a local token was the fastest path to solving a problem.
How I think about scoping now
The answer is not “never override tokens.” Some overrides are genuine. A site that has a brand guideline different from the network’s central branding is not a failure. But treating every override as equally cheap is what causes drift.
// Base layer — never override these. They define what the system is.:root { --color-bg-base: #f5f1e5; --color-text-base: #1a1a1a; --color-accent-base: #b8860b;}
// Component layer — site-wide adjustments to components.// Overriding here is still one decision, replicated everywhere.:root { --button-background: var(--color-accent-base); --button-foreground: white; --card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);}
// Instance layer — one-off adjustments.// Every override here is a site-specific decision that lives forever..hero-banner .button { --button-background: var(--color-text-base);}The practical benefit of writing it this way is that someone reading the CSS can see what is decided once (system, component) and what is decided per instance. The cost of the override becomes visible.
What I still don’t have
The one thing that I think is genuinely unresolved is how to make tokens expressive without making them infinite. A component that needs colour, padding, border-radius, font-size, font-weight, line-height, and text-transform is a component that needs nine tokens. Scale that to fifty components and you have four hundred tokens. Nobody can reason about four hundred tokens.
One approach I have seen work is compositional tokens — small, specific pieces that compose into larger decisions. Instead of --card-shadow, you have --shadow-sm, --shadow-md, --shadow-lg and a card uses var(--shadow-md). Instead of --button-font-weight, you have --weight-regular, --weight-bold and a button uses var(--weight-bold). The tokens shrink in count because they are orthogonal.
// Compositional tokens — ~30 tokens instead of ~400--space-xs: 0.5rem;--space-sm: 1rem;--space-md: 1.5rem;--space-lg: 2rem;
--weight-regular: 400;--weight-bold: 700;
--radius-sm: 2px;--radius-md: 4px;--radius-lg: 8px;
// Components compose them.button { padding: var(--space-sm) var(--space-md); font-weight: var(--weight-bold); border-radius: var(--radius-md);}This works better. The token set is small enough to comprehend. Components declare what they compose from, not what they are. A site that wants different spacing does not override four hundred tokens; it overrides eight.
But this requires discipline. A developer who does not understand the compositional philosophy will create component-specific tokens anyway, and you are back where you started.
I think the honest answer is that CSS variables are a tool that works up to a scale, and beyond that scale, you need a different approach — perhaps a token processor that validates override decisions, perhaps a design-system engine that generates tokens from a config file, perhaps a different architecture entirely. The variables themselves do not get worse. The system of decisions around them does.
If you have solved this at scale — if you have a multisite platform where token overrides are the exception and components remain coherent — I would genuinely like to understand how you enforced it.
What this came from
- ArticleCSS custom properties