The Three-State Toggle Trap
You've seen it: a theme switcher with Light, Dark, and System options. It looks thorough, but it often adds cognitive load without real benefit. The core question: do users really need a dedicated 'System' button when a well-designed two-state control can express all three states?
The argument is simple: if a user has no explicit preference, fall back to the OS setting. Provide an override only when the user actively chooses one. Store that choice in localStorage for future sessions. This reduces UI clutter and respects the user's primary intent—reading your content, not fiddling with themes.
A good two-state control can express all three states—users just need to apply the override the first time it becomes relevant.
This isn't just about minimalism. It's about understanding that theme switching is tangential to the user's goal. The less UI that accomplishes the same task, the better.

Implementing the Two-State Pattern
Here's a practical implementation using vanilla JavaScript and CSS. The toggle only shows the opposite of the current effective theme. If the system is light, show a 'Dark' button; if dark, show 'Light'. Clicking it sets an explicit override in localStorage.
// theme-toggle.js
const getPreferredTheme = () => {
const stored = localStorage.getItem('theme');
if (stored === 'light' || stored === 'dark') return stored;
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
const applyTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
// Update toggle label to show the *other* option
const toggle = document.getElementById('theme-toggle');
toggle.textContent = theme === 'dark' ? '☀️ Light' : '🌙 Dark';
};
const toggleTheme = () => {
const current = getPreferredTheme();
const next = current === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', next);
applyTheme(next);
};
// Initialize
document.addEventListener('DOMContentLoaded', () => {
applyTheme(getPreferredTheme());
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
});
// Listen for system changes if no override
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
applyTheme(e.matches ? 'dark' : 'light');
}
});
/* styles.css */
:root {
--bg: #ffffff;
--text: #000000;
}
[data-theme="dark"] {
--bg: #121212;
--text: #e0e0e0;
}
body {
background-color: var(--bg);
color: var(--text);
transition: background-color 0.2s, color 0.2s;
}
The toggle button can be a simple <button id="theme-toggle">🌙 Dark</button> in your header. No third option needed.

When Three States Actually Make Sense
A two-state toggle isn't a universal law. There are two scenarios where a three-state control (Light / Dark / System) is justified:
- Dedicated settings panel – If the toggle lives in a separate preferences page, not in the main navigation, users expect more granular control. The cognitive cost is lower because they're already in a 'settings' mindset.
- More than two color schemes – If your site offers multiple themes (e.g., sepia, high contrast, etc.), a simple two-state toggle can't express all options. Then a dropdown or a tri-state control becomes necessary.
Pitfalls to Avoid
- Don't show a 'System' option in the header – It distracts from the primary content. Users rarely need to explicitly revert to system after setting an override.
- Handle the 'revert to system' case gracefully – If a user toggles from Light (system) to Dark, then back to Light, your code should either store 'light' as an explicit override or clear the override. Both are acceptable; just be consistent. Most users won't notice until the OS theme changes, and fixing it is one click away.
- Test with
prefers-color-scheme– Ensure your CSS respects the media query when no override is set. UselocalStorageonly for explicit choices.
Remember: the theme toggle is not the user's primary intent. Optimize for content, not for control.

The Best Toggle Might Be No Toggle
The ultimate takeaway: if your site respects prefers-color-scheme and you don't offer a manual override, you're already ahead. A two-state toggle is a good compromise when you want to give users control without clutter. A three-state toggle is rarely worth the extra cognitive load.
Next steps: Audit your current theme switcher. Ask: does the 'System' option add real value, or is it just noise? Try the two-state pattern and measure user feedback. For more on designing with uncertainty and probabilistic thinking in AI-driven interfaces, check out our insight on designing with uncertainty. Also, if you're scaling modernization with AI agents, see how Azure and GitHub Copilot are changing the game.
Source: CSS-Tricks