Verified against @stacksjs/stx@0.2.119, dist/signals.js. Related to #1788 (same composable, different defect) and #1793.
This is less a bug than a missing capability that two in-flight proposals already assume exists.
The claim
useColorMode (signals.js:3762) is repeatedly put forward as the replacement for the hand-written theme-bootstrap script that apps inline in <head>:
<script>
;(() => {
const s = localStorage.getItem('theme') || 'system'
const d = s === 'dark' || (s === 'system' && matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', d)
document.documentElement.setAttribute('data-theme', d ? 'dark' : 'light')
})()
</script>It cannot replace it.
Why
That script exists for exactly one reason: it must run before first paint. It is deliberately render-blocking, placed above any stylesheet, and it touches the root element synchronously.
useColorMode is part of the client signals runtime. It reads localStorage at call time — after the runtime bundle has loaded and hydration has begun. Substituting it does not remove duplication so much as trade it for a guaranteed flash of the wrong theme on every cold load, for every user whose stored preference differs from the default. That is a strictly worse outcome than the duplication it set out to fix.
There is currently no hook that runs earlier: no SSR-time color-mode resolution, no documented way to emit a pre-paint snippet from config, no @head-level primitive that lands above the stylesheet.
Why this needs a decision rather than just a doc note
Two proposals in the standards RFC drafts are written on the assumption that the substitution works:
- The strict-mode proposal justifies a
no-bare-localstoragerule by arguing the barelocalStoragehits are all theme-boot duplication and thatuseColorMode"replaces it outright." As written, that rule would push apps toward a guaranteed theme flash. - The
stx newscaffold proposal ships a defaultstores/theme.tscontaininguseColorMode({ attribute: 'data-theme' })— which, per #1788, applies the attribute and never the class, so every new project would start with both defects on day zero.
What would resolve it
Any one of:
- A config-level pre-paint hook — something stx can inline into
<head>above the stylesheet, generated from the samestorageKey/attribute/classNamesoptionsuseColorModealready takes. This keeps one source of truth and removes the duplication the RFC is actually targeting. - SSR-time resolution, where the server writes the class/attribute onto the root element from a cookie so no client script is needed pre-paint.
- Failing either — document explicitly that
useColorModeis post-hydration only and that the inline snippet remains required, so the two proposals above get rewritten before they land.
I'd suggest (1): it is the only option that makes the strict-mode rule safe to enable, and it composes with #1788's fix rather than competing with it.