fix(frontend): declare color-scheme, so dark mode gets dark browser furniture - #77
Merged
Merged
Conversation
…urniture Split out of ADR-032 at the owner's instruction. That ADR asked a product question — should the product have a manual theme toggle — and found a defect while asking. The defect needed no product decision, so it should not have waited behind one. `globals.css` swapped 17 tokens and two shadows inside a `prefers-color-scheme` query, and that is everything *our* stylesheet paints. `color-scheme` was never declared, so everything the browser paints itself stayed light: scrollbars, autofill, native video controls, select popups, date pickers. `light dark` rather than a value per media query. It declares support for both and lets the browser follow the visitor's own preference, which is exactly the system-only behaviour ADR-032 keeps — a single value would override the choice the user already made in their operating system. **Measurement corrected the ADR, and the correction is in it.** §2 originally listed an `<input>`'s default background and a number input's spin buttons among what this fixes. Comparing a bare `<input>`, `<select>` and `<textarea>` inheriting `light dark` against the same three forced to `light`, with dark-scheme emulation on: **computed background, colour and border were identical.** Tailwind's Preflight already resets form controls to a transparent background and inherited colour, so that user-agent styling was never in play. The claim was wrong for this codebase and the list is now shorter and honest. **Two of my own test bugs, and the second is the one worth reading.** The first assertion was `/color-scheme\s*:/`, which also matches `prefers-color-scheme:` — so **it would have passed with the declaration entirely absent.** Its sibling, reading the value, matched the same media query and failed against a correct stylesheet; that failure is the only reason the silent one was found. Both now go through a `(?<!prefers-)` lookbehind, and the constant carries the story. A third: `new RegExp(... + "\s*...")` — `"\s"` in a JS string literal is just `s`, so the pattern meant "zero or more literal s" and worked by luck. Now `String.raw`. Five assertions, each provoked: removing the declaration fails three, forcing a single scheme fails one, and adding a `data-theme` selector fails the one that pins ADR-032's no-toggle decision — so if a toggle is ever built, that test is where the decision visibly changes. **Verified in a browser** with dark-scheme emulation, against a `rounded-full` control so an unloaded stylesheet could not be mistaken for a result: `getComputedStyle(document.documentElement).colorScheme` is `light dark`, `prefers-color-scheme: dark` matches, dark tokens active, and the declaration reaches the built CSS. **Not verified, and §2 now says so:** nobody has looked at a dark-mode scrollbar or an autofilled field. Neither is observable here — a scrollbar's colours are not exposed to `getComputedStyle` and autofill needs a real interaction. That is roadmap A4, which no longer gates this fix but is now its confirmation. ADR-032 stays `proposed` for §3's toggle question alone. STATUS and the roadmap note that **ADR-033 has the same shape** and could be split the same way: its logical-properties and `<track>` halves are separable from §4's deferral. 421 frontend tests across 36 files, tsc, eslint, verify:css, verify:a11y and verify:static all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out of ADR-032 at the owner's instruction. That ADR asks a product question — should there be a manual theme toggle — and found a defect while asking. The defect needed no product decision, so it should not wait behind one.
The defect
globals.cssswaps 17 tokens and two shadows inside aprefers-color-schemequery, and that is everything our stylesheet paints.color-schemewas never declared, so everything the browser paints itself stayed light: scrollbars, autofill, native video controls, select popups, date pickers.One declaration:
:root { color-scheme: light dark; }. Both values rather than one per media query — that declares support for both and lets the browser follow the visitor's own preference, which is exactly the system-only behaviour ADR-032 keeps. A single value would override the choice they already made in their OS.Measurement corrected the ADR, and the correction is in it
ADR-032 §2 originally listed an
<input>'s default background and a number input's spin buttons among what this fixes, labelled as reasoning from the spec rather than an observation. The observation contradicted it.Comparing a bare
<input>,<select>and<textarea>inheritinglight darkagainst the same three forced tolight, with dark-scheme emulation on: computed background, colour and border were identical in both cases. Tailwind's Preflight already resets form controls to a transparent background and inherited colour, so that user-agent styling was never in play here. The claim was wrong for this codebase; the list in §2 is now shorter and honest, and says which items are and are not observable.Two of my own test bugs, and the second is the one worth reading
The first assertion was
/color-scheme\s*:/— which also matchesprefers-color-scheme:, so it would have passed with the declaration entirely absent. Its sibling, reading the value, matched that same media query and failed against a correct stylesheet; that honest failure is the only reason the silent one was found. Both now go through a(?<!prefers-)lookbehind, and the named constant carries the explanation.A third, smaller:
new RegExp(... + "\s*...")—"\s"in a JS string literal is justs, so the pattern meant "zero or more literal s" and worked by luck. NowString.raw.Five assertions, each provoked
dark)data-themeselectorThat last one matters: if a toggle is ever built, that test is where the decision visibly changes rather than quietly eroding.
Verified, and not
Verified in a browser with dark-scheme emulation, against a
rounded-fullcontrol so an unloaded stylesheet could not be mistaken for a fix:colorSchemeon the root islight dark,prefers-color-scheme: darkmatches, dark tokens are active (--color-ink: #f1f2fc), and the declaration reaches the built CSS.Not verified, and §2 now says so: nobody has looked at a dark-mode scrollbar or an autofilled field. Neither is observable here — a scrollbar's colours are not exposed to
getComputedStyle, and autofill needs a real interaction. That is roadmap A4, which no longer gates this fix but is now its confirmation. The autofill case matters most, because a browser-painted background can drop text below the 4.5:1contrast.test.tscertifies — and that test cannot see a background the browser painted.Status of the ADR
ADR-032 stays
proposedfor §3's toggle question alone; §2 is marked extracted and fixed. STATUS and the roadmap note that ADR-033 has the same shape and could be split the same way — its logical-properties and<track>halves are separable from §4's deferral of localisation.421 frontend tests across 36 files, tsc, eslint,
verify:css,verify:a11y,verify:staticall pass.🤖 Generated with Claude Code