Foundations
Theme Provider
BetaSince 0.1.0
Light, dark or system theme for the whole app, remembered across visits and open tabs.
import { ThemeProvider, useTheme } from "@anantya/uix";Overview
ThemeProvider gives the whole app a light, dark or system theme. It follows the device's setting until the reader picks a theme, then remembers the choice across visits and open tabs. Mount one in the root layout, and read or change the theme anywhere below it with useTheme().
For a button that switches between light and dark, use Theme Toggle, which uses useTheme() for you.
Signature
function ThemeProvider(props: ThemeProviderProps): ReactElement;
interface ThemeProviderProps {
children?: ReactNode;
defaultTheme?: ThemeMode;
storageKey?: string;
nonce?: string;
tabbableLinks?: boolean;
}
function useTheme(): UseThemeReturn;
interface UseThemeReturn {
theme: ThemeMode;
resolvedTheme: ColorScheme;
setTheme: (theme: ThemeMode) => void;
}
type ThemeMode = "light" | "dark" | "system";
type ColorScheme = "light" | "dark";Parameters
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | The app. |
defaultTheme | "light" | "dark" | "system" | "system" | The theme until the reader picks one. system follows the device's setting. |
storageKey | string | "theme" | The localStorage key that keeps the reader's choice. |
nonce | string | - | The nonce for its inline script and style, when your Content Security Policy allows them by nonce. |
tabbableLinks | boolean | true | Gives links a tabindex="0", so Tab reaches them in Safari, which skips links by default. |
useTheme() takes no parameters.
Returns
ThemeProvider renders its children, after a LiveRegion, so announce() works anywhere in the app. On the server it also renders a small inline script that sets the theme before the first paint, so the page never shows the wrong one.
useTheme() returns:
| Name | Type | Description |
|---|---|---|
theme | "light" | "dark" | "system" | The reader's choice, or defaultTheme until they make one. |
resolvedTheme | "light" | "dark" | The theme shown, with system resolved to the device's setting. Changes when that setting does. |
setTheme | (theme) => void | Applies a theme and saves it as the reader's choice, for every open tab. |
useTheme() throws an error when there is no ThemeProvider above it.
Example
Mount ThemeProvider inside <body> in your root layout, such as app/layout.tsx in Next.js:
import { ThemeProvider } from "@anantya/uix";
import type { ReactNode } from "react";
import "./globals.css";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}On a settings page, a Switch sets system with useTheme(), beside a Theme Toggle for light
and dark. Each switches this whole page.
Showing the light theme
Code
"use client";
import { Switch, ThemeToggle, useTheme } from "@anantya/uix";
export default function MatchDevice() {
const { theme, resolvedTheme, setTheme } = useTheme();
return (
<div className="flex flex-col gap-3 text-sm">
<label className="flex items-center gap-3 font-medium">
<Switch
checked={theme === "system"}
onCheckedChange={(checked) => setTheme(checked ? "system" : resolvedTheme)}
/>
Match my device
</label>
<p className="flex items-center gap-2 text-muted-foreground">
Showing the {resolvedTheme} theme
<ThemeToggle size="sm" variant="secondary" />
</p>
</div>
);
}Notes
- ThemeProvider sets the
darkclass on<html>. The page's HTML from the server doesn't have it, so give<html>suppressHydrationWarning, as above - On the server and until hydration is done,
useTheme()returnsdefaultTheme, andresolvedThemeis light forsystem. Show what depends on the theme withdark:classes instead, which are right from the first paint - When the browser blocks localStorage, the choice lasts until the reader leaves the page
- A change cross-fades the page once, where the browser has View Transitions. It is instant when the reader prefers reduced motion
- Mount one ThemeProvider per app
Changelog
Every change to Theme Provider since 0.1.0 is in the release notes.