ThemeProvider renders the themed element as a plain div inside
#root:
return (
<ThemeContext.Provider value={value}>
<div data-theme={theme}>{children}</div>
</ThemeContext.Provider>
)
and every theme variable — including color-scheme — is declared on that
selector:
[data-theme='dark'] { --bg: #0b0d10; ... color-scheme: dark; }
color-scheme and the page background are the two things a browser reads
off the root element, not off a div, so neither reaches the document.
Measured against npm --prefix site run dev:
getComputedStyle(document.documentElement).colorScheme // "normal"
getComputedStyle(document.body).colorScheme // "normal"
getComputedStyle(themedDiv).colorScheme // "dark"
getComputedStyle(document.documentElement).backgroundColor // rgba(0, 0, 0, 0)
getComputedStyle(document.body).backgroundColor // rgba(0, 0, 0, 0)
getComputedStyle(document.querySelector('.app-shell')).backgroundColor // rgb(11, 13, 16)
So the only thing painting the site's background is .app-shell, and the
canvas behind it belongs to the visitor rather than to the site. Emptying
#root — which is exactly the markup index.html ships, and exactly what
the browser paints between parsing the HTML and the deferred
src/main.jsx module mounting React — shows what that canvas is:
- with the browser in light mode: a white page
- with the browser in dark mode: a dark page
Reproduce both by toggling the emulated prefers-color-scheme in DevTools
(Rendering panel) and running document.getElementById('root').innerHTML = ''.
Three consequences, all for a visitor whose browser is in light mode,
which is most of them:
- a white flash on every cold load, before the bundle mounts the dark UI;
- the overscroll/rubber-band area above and below the page is white while
the page is near-black;
- UA-drawn scrollbars and controls render light against the dark page,
because color-scheme: dark never reached the root.
The theme toggle changes none of it — it flips an attribute the canvas
cannot see.
The desktop app already does this right: app/src/styles.css:3 puts
color-scheme on :root. The fix here is the same shape — set the
attribute on document.documentElement from a useEffect in
ThemeProvider instead of wrapping the children in a div, and give
html, body background: var(--bg) so the canvas is painted too.
Verified in the running dev server: setting data-theme="dark" on <html>
and adding body { background: var(--bg) } gives
getComputedStyle(document.documentElement).colorScheme === "dark" and
paints the canvas rgb(11, 13, 16) with #root still empty and the
browser still in light mode.
site/src/context/ThemeContext.jsx:26 · site/src/styles/index.css:6 · site/src/styles/index.css:43
ThemeProviderrenders the themed element as a plaindivinside#root:and every theme variable — including
color-scheme— is declared on thatselector:
color-schemeand the page background are the two things a browser readsoff the root element, not off a div, so neither reaches the document.
Measured against
npm --prefix site run dev:So the only thing painting the site's background is
.app-shell, and thecanvas behind it belongs to the visitor rather than to the site. Emptying
#root— which is exactly the markupindex.htmlships, and exactly whatthe browser paints between parsing the HTML and the deferred
src/main.jsxmodule mounting React — shows what that canvas is:Reproduce both by toggling the emulated
prefers-color-schemein DevTools(Rendering panel) and running
document.getElementById('root').innerHTML = ''.Three consequences, all for a visitor whose browser is in light mode,
which is most of them:
the page is near-black;
because
color-scheme: darknever reached the root.The theme toggle changes none of it — it flips an attribute the canvas
cannot see.
The desktop app already does this right:
app/src/styles.css:3putscolor-schemeon:root. The fix here is the same shape — set theattribute on
document.documentElementfrom auseEffectinThemeProviderinstead of wrapping the children in a div, and givehtml, bodybackground: var(--bg)so the canvas is painted too.Verified in the running dev server: setting
data-theme="dark"on<html>and adding
body { background: var(--bg) }givesgetComputedStyle(document.documentElement).colorScheme === "dark"andpaints the canvas
rgb(11, 13, 16)with#rootstill empty and thebrowser still in light mode.
site/src/context/ThemeContext.jsx:26·site/src/styles/index.css:6·site/src/styles/index.css:43