Skip to main content

On This Page

Two Color Schemes, Four Modes: Native CSS Theme Switching

2 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Two Color Schemes, Four Modes: Native CSS Theme Switching.

Olga Urentseva demonstrates a native CSS approach to managing four distinct theme variants through the light-dark() function. The system enables a default and a secondary color scheme, each supporting light and dark modes with zero JavaScript for color values.

Why This Matters

Modern frontend development is shifting toward vanillaization to leverage native browser features, reducing reliance on heavy JavaScript state management. While @container style() queries represent an ideal model for theme switching, current lack of support in Safari and Firefox makes specificity-based CSS overrides the more technical reality for cross-browser production environments.

Key Insights

  • The :root:root:root specificity hack prevents global CSS variables from being overridden by injected styles from tools like styled-components.
  • Native light-dark() functions allow browsers to automatically select values based on system preferences without JavaScript intervention.
  • Applying theme classes synchronously in the main entry point (main.tsx) prevents the flash of unstyled content (FOUC) on page reload.
  • CSS specificity hierarchy allows .spring:root:root:root to reliably override default :root variables when a class is applied to the HTML tag.
  • Browser compatibility limitations currently prevent the use of @container style() queries in Firefox Developer Edition and Safari as of 2026.

Working Examples

Specificity-based theme overrides using native light-dark() functions.

:root:root:root {
  --color-background: light-dark(oklch(0.99 0.0105 320.98), oklch(13.709% 0.02553 268.319));
  --color-primary: light-dark(oklch(70.61% 0.085 271.69), oklch(0.79 0.1233 266.14));
}

.spring:root:root:root {
  --color-background: light-dark(oklch(0.99 0.012 150), oklch(0.18 0.05 145));
  --color-primary: light-dark(oklch(56.316% 0.10067 150.907), oklch(0.72 0.2 145));
}

Zero-state UI toggle logic using native DOM classList API.

function handleToggle() {
  const isSpring = document.documentElement.classList.toggle("spring");
  localStorage.setItem("theme", isSpring ? "spring" : "default");
}

Practical Applications

  • Vite-based production builds: Use a single CSS file with class-based overrides to avoid the unreliability of asynchronous dynamic CSS imports.
  • Pitfall: Implementing themes via React state or Context can lead to unnecessary re-renders and layout shifts compared to direct DOM class manipulation.
  • Legacy Integration: Use the triple :root selector to ensure global variables maintain priority in projects still utilizing styled-components or other CSS-in-JS libraries.
  • Pitfall: Relying on light-dark() without @media (prefers-color-scheme) fallbacks will result in broken themes on browsers lacking modern CSS support.

References:

Continue reading

Next article

Solving Gitaly Memory Spikes: Why Cgroup v2 is Critical for GitLab on Kubernetes

Related Content