Let me tell you a story. This is when I was working at a startup.
It starts like most do: on a Friday, with good intentions and a small, seemingly harmless CSS tweak...
🧪 The Setup
We had just rolled out a long-awaited design system refresh. Among the many visual improvements: cleaner spacing, a refined color palette, and (drumroll) CSS custom properties — aka, variables.
It looked something like this:
:root {
--primary-color: #1a73e8;
--button-padding: 0.75rem 1.5rem;
}
We used these everywhere. Clean, reusable, and easy to override — what's not to love?
Except… one tiny variable was waiting to cause chaos.
⚙️ The Change
A designer requested a quick adjustment:
"Can we tone down the primary button color just a bit?"
Easy. I updated --primary-color
to a new hex value in the design token file:
--primary-color: var(--color-primary-base);
--color-primary-base
was defined in a separate file, but I was confident everything was wired up. After all, we had a theme system, layered variables, and fallbacks. What could go wrong?
I pushed the change. CI passed. Code deployed. Weekend mode: activated.
🔥 The Crash
Within minutes, the support channel lit up.
“Buttons are invisible.”
“I can’t click anything.”
“Login page is blank.”
Wait, what?
I opened the site. The buttons were gone. Not just colorless — completely invisible. No background. No border. Just floating text. In some places, not even that.
🕵️♂️ The Investigation
After some rapid-fire inspection in DevTools, I saw this:
background-color: var(--primary-color);
...but --primary-color
was resolving to... nothing. undefined
. Empty. No fallback.
Why?
Because I mistakenly assumed --color-primary-base
was always defined.
But in production, due to conditional theming logic and a bundler optimization, it was missing from the root scope. 😬
As a result:
--primary-color: var(--color-primary-base);
...evaluated to --primary-color:
(i.e., empty), and so background-color
inherited nothing.
No fallback. No warnings. Silent failure. And boom — the UI melted.
💡 The Fix
I hotfixed it by adding a default fallback:
--primary-color: var(--color-primary-base, #1a73e8);
Then updated the build step to ensure all token layers were correctly bundled and available in the final CSS.
Crisis averted. Lessons painfully learned.
🧠 What I Learned
1. CSS Variables Don’t Fail Loudly
Unlike JavaScript, CSS doesn’t throw. If a variable is missing, it silently fails and can break styles in subtle (or dramatic) ways.
2. Always Use Fallbacks
color: var(--text-color, black);
Always assume a variable might be missing — especially in dynamic themes, design systems, or multi-team environments.
3. Linting Can Save You
A linter or PostCSS plugin that validates custom properties and detects missing ones would’ve caught this. Use tools to catch what your eyes miss.
4. Test Like Production
Don’t assume your local/staging setup reflects real-world conditions. Tree-shaking or conditional loading might alter what CSS is available in production.
🎯 Bonus Tip: Use CSS Custom Properties Intentionally
They’re powerful — great for theming, dynamic UI changes, and reducing repetition.
But they’re also fragile when layered, abstracted, or conditionally injected.
Use them wisely. And don’t forget the humble fallback value.
🫣 Final Thought
This little bug cost us 45 minutes of downtime, a Friday evening fire drill, and a new entry in the postmortem doc titled “CSS Can Break Production Too.”
The moral of the story? Even the smallest change in a CSS variable can ripple into disaster.
So next time you write:
--something: var(--something-else);
Ask yourself: What if that’s undefined?
And maybe — just maybe — you’ll avoid your own invisible button catastrophe.
Top comments (12)
Invisible buttons and Friday fire drills are such a nightmare - I've been bitten by silent CSS variable fails too.
Did you end up adding any custom linter rules or automated checks after that?
Glad (and sorry?) to hear I’m not alone in the CSS variable pain club. 😅
Yep. We added a stylelint rule to catch CSS variables without fallbacks.
Something like:
This flags any usage of var(--some-var) without a fallback (i.e., missing the , fallback part).
Simple but super effective — saved us a few times since!
Beautifully written!
Thanks @javascriptwizzard
"CSS variable pain club. 😅" ... congrats, you've earned a very new badge today!

The moral of the story is: never deploy on Friday.
Great post!
Thanks @michael_liang_0208 !
I wonder if the bug can detected early if specific linters for CSS such as style lint is used for checking.
Yes @kc900201 , Thats what we did afterwards. We added a stylelint rule to catch CSS variables without fallbacks:
been there, man. the number of times i’ve watched a 'harmless' tweak nuke something is too high. respect for sharing the pain - helps me remember to double check for those fallbacks.
Thanks @nathan_tarbert !