Web platform

A theme toggle should not overwrite the user's OS choice

A theme toggle should not overwrite the user's OS choice

Someone told their laptop to go dark at sunset. They set that once, in the OS. Then they land on your site at nine in the morning, tap the theme toggle to pick light, and by dinner the page is dark again. Not because they asked. Because your toggle mapped "light" back to "system" behind the scenes.

Bramus Van Damme's new post makes the case for the third button, the one Lea Verou's recent post argued you could drop.

What the article is actually calling out

The piece is framed openly as a reply to "two states are enough". Per the source, the failure mode of a two-state toggle is a mapping problem: one of its values has to double as "follow the OS", and that decoding step is where the surprise lives. Bramus puts it in one sentence: "Because the two-state toggle maps one of its values back to 'System' behind the scenes, the site is now dark, although they explicitly chose light the last time they interacted with the control."

That is a real user, in the middle of a real day, watching an explicit choice get overwritten by a schedule they never brought to your page.

The user's setting is already an answer

Someone who configures automatic switching at the OS has said, plainly: change this on a schedule. A two-state control cannot tell the difference between "I want light forever" and "I want whatever the OS says right now". Both taps look identical to it.

Per the source, that is the whole reason for the tri-state pattern. Bramus's line is worth pulling: "Tri-state prioritizes clarity over brevity." The extra option is not clutter. It is the honest label for a state the storage model already carries.

Two controls, three states: the Vale pattern

The post cites an alternative worth pausing on. Per the source, Vale exposes two visible controls but three functional states: neither highlighted means System, clicking Light or Dark pins that choice, and clicking the pinned control again releases the pin and returns to System.

That is close to what accessibility review usually asks of a toggle. No control should lie about its role. The current policy, not just the current pixel colour, has to be legible from the widget itself. If a user cannot tell whether they are pinned or floating with the OS, the widget is missing information the user needs to reason about their own preference.

Storage, done honestly

Per the source, if you keep a two-state control for space reasons, store an explicit "light" or "dark" value and never store "system" as a value that later code has to decode. Bramus's stronger recommendation is to use three explicit values and let a tri-state control read and write them directly. Either way, the rule the article draws out is the same: store what the user meant, not what the page currently shows.

Verdict

The two-state camp is not wrong that a header is not a settings panel, and small chrome deserves careful controls. But small chrome is not a licence to silently overwrite an OS preference the user has already made. Bramus's counterpoint is fair, and the Vale pattern is a genuine middle path: same footprint, honest states.

Before you ship a theme toggle:

  • Do not map "user tapped light" to "clear the override and follow the OS".
  • Give the user a way to say "follow the OS" that survives a schedule flip.
  • Store the user's intent, not the current appearance.
  • Reassess the theme on user interaction, not on OS change.

Source: Bram.us (bram.us)

Related
Web platform

Your theme toggle does not need a System button

Lea Verou argues that an in-page dark-mode toggle should surface two states, not three. The System option describes the data model, not what the user is actually trying to do.

August 7, 2026
Web platform

Your site does not need to function the same on every platform

Bramus argues authors have swapped one cross-browser obsession for a new one — that sites must behave identically across mouse, touch, keyboard and Vision Pro. The piece reframes input-specific affordances as progressive enhancement on the input axis.

June 25, 2026
Accessibility & semantics

Whether a dialog closes on outside click is a task question, not a style question

Adrian Roselli sorts modal close behaviour into three buckets — action required, no action required, and weird patterns — and argues you should pick a light-dismiss policy based on what the user came to do, not on how the dialog looks.

August 13, 2026