Files
skills/docs/ui/react/react-ui/src/theme/theme-context.md
2026-05-29 08:20:56 +01:00

11 KiB

theme-context — React context plumbing for the active theme

(no submodules)

Purpose

Owns the React Context <ThemeProvider> writes into and every theme hook (useTheme, useThemeTokens, useThemedVariants, <ThemeSwitcher>) reads from. The context is intentionally typed as ThemeContextValue | null — null carries "no Provider mounted" so hooks can throw a structured ReactUiError(REACT_UI.NO_PROVIDER) in dev mode rather than silently rendering a broken UI.

Features

  • Single source-of-truth context for the chassis theme system
  • null discriminator drives provider-missing detection in every consumer hook
  • displayName set so React DevTools shows ReactUiThemeContext rather than the unhelpful Context.Provider default
  • Exposes reducedMotionActive so motion-aware components can read the resolved prefers-reduced-motion policy without re-querying matchMedia themselves

Usage: Basic example

import { useContext } from "react";
import { ThemeContext } from "@suaveplan/react-ui/theme/theme-context";

function CustomThemeAware() {
    const ctx = useContext(ThemeContext);
    if (!ctx) return null;
    return <div data-theme={ctx.theme.name}>…</div>;
}

Usage: Advanced example

import { useContext } from "react";
import { ThemeContext, type ThemeContextValue } from "@suaveplan/react-ui/theme/theme-context";

function useThemeOrThrow(): ThemeContextValue {
    const ctx = useContext(ThemeContext);
    if (ctx === null) {
        throw new Error("useThemeOrThrow requires <ThemeProvider>");
    }
    return ctx;
}

API Reference

  • ThemeContext: React.Context<ThemeContextValue | null> — initial value null so unwrapped reads can be detected
  • interface ThemeContextValue — { theme, themes, setTheme, isPending, reducedMotionActive }

Implementation notes

The setTheme callback is typed as (name: string) => Promise<void> so the Provider can wrap the underlying state update in useTransition() and consumers that await setTheme('dark') get the DOM in its post-commit state. The reducedMotionActive field is recomputed inside the Provider on prefers-reduced-motion media query changes; consumers that need finer control over the motion policy should read theme.motionTokens.respectReducedMotion directly via useTheme().