How to Implement Dark Mode with Zero JavaScript
Eliminating the Flash of Unstyled Content
Adding dark mode support to a web application has transitioned from a nice-to-have visual enhancement to a standard user expectation. Historically, developers implemented dark mode by adding a click listener to a toggle button, using JavaScript to add a "dark" class to the "body" element, and storing the preference in "localStorage". While simple, this approach suffers from a significant user experience flaw: the Flash of Unstyled Content (FOUC).
Because JavaScript execution occurs after the browser parses the HTML and CSS, a visitor with a dark theme preference will often see a bright white flash before the script loads and applies the dark mode class. To avoid this, developers resorted to blocking inline scripts placed in the HTML "head". However, this blocks HTML parsing and hurts PageSpeed scores.
Modern CSS custom properties and advanced pseudo-classes allow engineers to build a fully interactive dark mode system that respects system preferences and allows manual user overrides without a single line of JavaScript. This approach keeps pages lightweight, eliminates rendering flashes, and runs natively inside the browser's style engine.
Establishing CSS Custom Property Foundations
The foundation of a JS-free theme system is CSS custom properties (variables) combined with the "@media (prefers-color-scheme)" media query. At Bramsley Digital Studio, we define our theme variables at the ":root" level. First, we establish the default light theme.
Next, we override these variables inside a dark-mode media query. This ensures that browsers automatically render the correct theme based on the user's operating system settings before the first paint:
:root {
--bg-primary: #ffffff;
--text-primary: #1a1a1a;
--accent-color: #3b82f6;
--border-color: #e5e7eb;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #0f172a;
--text-primary: #f8fafc;
--accent-color: #60a5fa;
--border-color: #334155;
}
}
body {
background-color: var(--bg-primary);
color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease;
}
Designing a Zero-JS Interactive Theme Override
While system-based styling covers the majority of use cases, user experience research shows that users expect a toggle switch to manually override their system setting. To implement this without JavaScript, we can leverage an HTML checkbox and the CSS ":has()" relational selector.
The ":has()" selector, supported in all modern browsers, allows styling parent elements based on the state of their children. By placing a checkbox at the top of the HTML hierarchy, we can detect when it is checked and dynamically update the root custom properties.
Let's examine the HTML structure and CSS configuration required to implement this override system:
<!DOCTYPE html>
<html lang="en">
<head>
<style>
:root {
--bg-primary: #ffffff;
--text-primary: #1a1a1a;
--accent: #2563eb;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #121212;
--text-primary: #e0e0e0;
--accent: #3b82f6;
}
}
/ Hide the checkbox visually but keep it keyboard accessible /
.theme-checkbox {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/ Custom variables override using :has() /
html:has(.theme-checkbox:checked) {
--bg-primary: #121212;
--text-primary: #e0e0e0;
--accent: #3b82f6;
}
@media (prefers-color-scheme: dark) {
html:has(.theme-checkbox:checked) {
--bg-primary: #ffffff;
--text-primary: #1a1a1a;
--accent: #2563eb;
}
}
/ Style the toggle label button /
.toggle-label {
cursor: pointer;
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
border: 1px solid var(--border-color);
border-radius: 9999px;
background-color: var(--bg-primary);
color: var(--text-primary);
user-select: none;
}
</style>
</head>
<body>
<input type="checkbox" id="theme-toggle" class="theme-checkbox" />
<main>
<header>
<h1>Zero JavaScript Dark Mode</h1>
<label for="theme-toggle" class="toggle-label">
Change Theme
</label>
</header>
<p>This page implements interactive theme switching without any scripting.</p>
</main>
</body>
</html>
In this implementation, the ":has()" relational selector is evaluated at the "html" root element level. When a user clicks the label, the browser checks the input box. The style engine immediately recalculates the CSS variables for the entire document.
If the user's system has a light preference (the default state) and they check the box, the style rules under "html:has(.theme-checkbox:checked)" apply, switching the variables to dark values. Conversely, if the user's system runs dark mode by default, the media query rules are active, and checking the toggle activates the overrides inside the media query block, reverting the values back to light colors.
Advantages of Pure CSS Theme Orchestrations
Using pure CSS for theme management has multiple advantages:
- Flicker-Free Render: Because styles are computed during layout phase, there is no latency Flash of Unstyled Content (FOUC). This ensures that visitors never experience jarring white light transitions.
- Improved Core Web Vitals: Eliminating runtime theme-switching scripts lowers First Input Delay (FID) and Interaction to Next Paint (INP) by reducing execution threads.
- Edge-Cache Compatibility: The HTML markup remains completely static and uniform, allowing aggressive caching on global edge networks since no user-specific HTML injection is needed.
- Accessibility Compliance: Standard input forms integrate seamlessly with screen readers and keyboard tab navigation out-of-the-box.
Visual Interface Optimization at the Edge with Bramsley
Creating polished user interfaces requires combining technical optimization with responsive layout design. At Bramsley, we craft premium web experiences that prioritize visual performance and semantic structure. We utilize clean, pure CSS structures and advanced styling selectors to implement features like zero-JS theme toggling, preventing layout shifts and rendering lag across global edge distributions.
By eliminating client-side scripting overhead, we ensure your site is incredibly fast and highly accessible. Partner with us to build your next high-performance frontend application.