Skip to content

Themes

Dark by default; switch the page or any part of it to light with one attribute.

tokens.css defines every token for the dark theme on :root and redefines the ones that differ for the light theme under [data-theme="light"]. The styled components only read tokens, so they follow the theme with no extra setup.

Choose a theme

<!-- The whole page in the light theme -->
<html data-theme="light">
  …
</html>

<!-- One region in light, and a dark island inside it -->
<section data-theme="light">
  …
  <aside data-theme="dark">…</aside>
</section>

Each theme also sets color-scheme, so native form controls and scrollbars match.

Set the theme on <html> rather than on an app wrapper: components that render overlays into document.body, such as menus and tooltips, take the theme from there.

Switch at runtime

ThemeProvider keeps the theme on <html> for you: the system theme until the user chooses one, then their choice, stored in localStorage and restored on the next visit. Render it once, around the app:

import { ThemeProvider } from '@contour-ui/react';

<ThemeProvider>
  <App />
</ThemeProvider>;

Below it, useTheme reads the theme and changes it. theme is the user's choice, system included; resolvedTheme is the theme in use, light or dark:

theme-switch.tsx
import { Button, useTheme } from '@contour-ui/react';
import type { ThemeChoice } from '@contour-ui/react';

const choices: ThemeChoice[] = ['system', 'light', 'dark'];

export function ThemeSwitch() {
  const { theme, setTheme } = useTheme();

  return (
    <fieldset>
      <legend>Theme</legend>
      {choices.map((choice) => (
        <Button
          key={choice}
          variant={theme === choice ? 'solid' : 'outline'}
          aria-pressed={theme === choice}
          onClick={() => setTheme(choice)}
        >
          {choice}
        </Button>
      ))}
    </fieldset>
  );
}

A page rendered on the server also needs the script of themeScript() at the top of its <head>, so the browser paints it in the right theme before the app runs: see Set up. For code of your own, @contour-ui/tokens exports the theme names (themes, defaultTheme) and the attribute (themeAttribute), so it stays in sync with the stylesheet.

Headless

Headless ships no styles, so it has no themes: theme your own CSS however you like, or use the token variables and the same data-theme attribute.