Theme Provider

Supplies theme context and drives the light/dark class on the document.

ThemeProvider holds the current theme — system, light, or dark — persists it to localStorage, and toggles the dark class on <html> so tokens resolve correctly. Wrap your app root in it once; anything that reads or sets the theme does so through the useTheme hook it exposes.

Theme

<ThemeProvider>
<ThemeToggle />
</ThemeProvider>

When theme is system, the provider follows the OS preference live and updates as it changes. Setting light or dark pins the choice and stores it.

Theme changes are instant. The provider disables CSS transitions for the frame it swaps the dark class, so components with colour transitions don't fade between themes one by one. See Motion.

useTheme

import { useTheme } from '@base-org/bds-web/product/theme-provider';

const { theme, setTheme, resolvedTheme } = useTheme();

theme is the user's choice (including system), resolvedTheme is the light or dark actually applied, and setTheme changes it. Calling useTheme outside a ThemeProvider throws.

When to use

Use ThemeProvider once at the app root in any app that supports light and dark mode.

To let the reader pick a theme, render a Theme Toggle inside it.

Usage

Do

Render exactly one ThemeProvider at the root. It owns the dark class on <html>, and nesting a second one gives you two sources of truth.

Don't

Toggle the dark class or write to the theme key yourself. Go through setTheme so the stored value and the applied class stay in sync.

Props

ThemeProvider takes a single prop, children. The active theme is read from localStorage and the OS colour-scheme preference, not from props.

Accessibility

ThemeProvider defaults to system, so a reader who has set a dark OS preference gets dark mode without touching anything.

ThemeProvider applies the dark class in an effect after the first render, so on its own a dark-mode reload briefly paints light. To avoid the flash, add a small inline script to <head> that reads the same theme key from localStorage and adds the dark class before paint (falling back to prefers-color-scheme for system or no stored value). ThemeProvider then takes over after hydration.