Theming
Dark mode
Every token ships a light and a dark value. Dark mode is a single attribute on the html element — no .dark class per component, no media query in the library.
The mechanism
Set data-theme="dark" (or "light") on <html>. The token stylesheet emits both a base block and a :where(:root[data-theme="dark"]) block, so flipping the attribute re-values every --oks-* property at once.
document.documentElement.setAttribute("data-theme", "dark");Respecting the system preference
With no attribute set, fall back to the OS setting in your own CSS:
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
/* your dark overrides, if any */
}
}No flash of the wrong theme
Apply the stored / preferred theme before the page paints with a tiny blocking script in <head>. Without this, a user who prefers dark sees a white flash on every load.
<script>
(function () {
try {
var stored = localStorage.getItem("theme");
var dark = stored === "dark" ||
(!stored && matchMedia("(prefers-color-scheme: dark)").matches);
document.documentElement.setAttribute("data-theme", dark ? "dark" : "light");
} catch (e) {}
})();
</script>layout.tsx via next/script with strategy="beforeInteractive", and add suppressHydrationWarning to the <html> element (the script mutates it before React hydrates).Consumer-defined tokens
A few tokens are deliberately left to you and default to light values: --oks-color-surface and the --oks-form-field-* group. If your app has dark mode, set dark values for these under your dark selector so overlays and form fields match.