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.

UI designer sketching dark mode toggle options on a whiteboard with two-state vs three-state diagrams Software Concept Art

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.

Smartphone screen displaying a website header with a dark mode toggle switch and system preference icon Dev Environment Setup

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:

  1. 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.
  2. 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. Use localStorage only for explicit choices.

Remember: the theme toggle is not the user's primary intent. Optimize for content, not for control.

Developer workspace with monitor showing dark mode UI design and code editor side by side IT Technology Image

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

This content was drafted using AI tools based on reliable sources, and has been reviewed by our editorial team before publication. It is not intended to replace professional advice.