Five Themes, One CSS Variable System (and the Bug That Broke All of Them)
Building a sitewide theme switcher taught me that CSS custom properties scale beautifully — and fail completely silently.
This site ships with five accent palettes — Obsidian, Sunset, Matrix, Synthwave, Crimson — switchable from a command palette and persisted in localStorage. The architecture is the standard one done thoroughly: every accent color in the design system resolves through CSS custom properties on :root, components never hardcode accent values, and switching themes is just swapping one set of property values. Framer Motion animations, glass-card borders, gradient text — all of it re-skins instantly because nothing references a literal color.
The variables come in pairs: a ready-to-use color and an RGB triplet like --theme-primary-rgb, because rgba() compositing needs raw channel values to apply per-use alpha. That second form is where the bug lived. Somewhere during a refactor, a batch of styles ended up as rgba(var(--theme-primary-rgb), ) — the alpha argument just gone, a dangling comma where a number should be.
Here is the part that makes CSS different from every other language I work in: that is not an error. Nothing throws. Nothing logs. The declaration is invalid, so the browser silently drops it and the element falls back to whatever it inherits — which in a dark-themed site often looks almost right. The damage was scattered across the whole site as subtly-wrong borders and glows that I half-noticed for a while before connecting them. TypeScript would have refused to compile; CSS just shrugged.
The cleanup was a sitewide grep for the malformed pattern, and the takeaway is now a personal rule: any mechanical refactor touching CSS gets a pattern-search audit afterward, because the browser will never tell you what it discarded. Strict compilers spoil you. CSS keeps you humble.