Skip to content

Dark mode

Every token ships a light and a dark value. Dark mode is a single attribute on the html element, with no .dark class per component. The only media query in the library is in the block editor, which follows the OS setting when no attribute is set.

LiveOverview

Try it on this page

One attribute on the html element flips every token.

Success
Danger
<html data-theme="dark">

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>

Surfaces and form fields follow the theme too

--oks-color-surface and --oks-form-field-bg have dark values built in, and the other --oks-form-field-* tokens are built from the neutral palette, which flips in dark mode. You only need to set them yourself if your brand uses different surfaces, and if you do, set both the light and the dark value.