Dark Mode

Light values sit on :root. Dark values apply through :root[data-color-scheme="dark"] and, when the page is not pinned, through prefers-color-scheme: dark on :root:not([data-color-scheme="light"]) — see src/styles/abstracts/_css-vars.scss.

How the scheme is chosen
  1. The root layout renders <html lang="en" data-color-scheme="dark">, so the document defaults to dark before hydration.
  2. SiteShell accepts colorScheme. When set, PageChrome writes the attribute on every route change — a pinned scheme always wins over the saved preference and the OS setting.
  3. Every page shipped with Rasuke 1.0 pins "dark" (including the 404). Remove colorScheme from a page (and the layout default) to let that page follow the visitor's system + saved choice.
<SiteShell pageName="about" bodyClass="page-about" colorScheme="dark" headerName="header-1" footerName="footer-1">
  {/* sections */}
</SiteShell>
Runtime toggle

public/assets/js/vendors/color-modes.js is registered before paint with React 19's preinit() in src/app/layout.tsx. It:

  • looks for [data-color-scheme-toggle] buttons (for example the offcanvas menu theme switch) and syncs aria-pressed + aria-label (data-label-to-light / data-label-to-dark);
  • persists the choice in localStorage under rasuke-color-scheme;
  • applies a stored choice on load; a page that pins colorScheme re-asserts it in PageChrome after hydration, so the pinned scheme is what the visitor keeps;
  • adds the js class before the first frame, so reveal styles never flash;
  • dispatches a rasuke:color-scheme event so canvases that rasterized currentColor (for example the hero wordmark) can re-rasterize.
Write mode-safe CSS

Sections never reference a mode. Bind semantic tokens and they flip automatically:

.about-2 {
  background-color: var(--color-surface-subtle);
  color: var(--color-text-primary);
  border-color: var(--color-border-base);
}

Do not write a dark-only hex in SCSS. If a new color must differ by mode, add both light and dark values in tokens.json. The brand orange #FF6A3A, cream #FCF7F3, and footer surface #1B1B1B are intentionally identical in both modes.

Testing both modes
  • Temporarily remove colorScheme from a page and toggle the theme from the offcanvas menu.
  • Force a mode in DevTools → Rendering → Emulate CSS prefers-color-scheme to check the unpinned fallback.
  • Clear the rasuke-color-scheme localStorage key to reset a saved choice.
  • Re-check WebGL sections: deformation canvases sample images, so they need to rebuild when the mode changes.