Perfect theming for any React framework
SSR-friendly theming for React using cookies - with system preference, cross-tab sync, no flash, and a strongly typed useTheme hook.
bun add ssr-themesLive demos
Theme picker
Pick a palette and the UI updates instantly.
Hydration-safe changes that sync across tabs.
Usage example
Drop in
themeScript() + ThemeProvider to get no-flash theming.// src/routes/__root.tsx
import {createServerFn} from '@tanstack/react-start';
import {getRequestHeader} from '@tanstack/react-start/server';
import {
HeadContent,
Outlet,
ScriptOnce,
Scripts,
createRootRoute,
} from '@tanstack/react-router';
import {ThemeProvider} from 'ssr-themes';
import {
registerTheme,
themeFromCookieHeader,
themeScript,
} from 'ssr-themes/server';
const getInitialTheme = createServerFn({method: 'GET'}).handler(
() => themeFromCookieHeader(getRequestHeader('cookie')),
);
function RootComponent() {
const {initialTheme} = Route.useLoaderData();
const theme =
initialTheme && initialTheme !== 'system'
? initialTheme
: undefined;
return (
<html suppressHydrationWarning {...registerTheme({theme})}>
<head>
<HeadContent />
</head>
<body>
<ThemeProvider initialTheme={initialTheme}>
<ScriptOnce children={themeScript()} />
<Outlet />
</ThemeProvider>
<Scripts />
</body>
</html>
);
}
export const Route = createRootRoute({
loader: async () => ({
initialTheme: await getInitialTheme(),
}),
component: RootComponent,
});
Forced theme routes
Use route staticData to force a theme per route.
// src/routes/dark.tsx
import {createFileRoute} from '@tanstack/react-router';
export const Route = createFileRoute('/dark')({
staticData: {theme: 'dark'},
component: () => <div>Always dark</div>,
});
// src/routes/__root.tsx
import {useMatches} from '@tanstack/react-router';
import {ThemeProvider, type SystemTheme} from 'ssr-themes';
const matches = useMatches();
const forcedTheme = matches.reduce<SystemTheme | undefined>(
(theme, match) => {
const staticData = match.staticData as
| {theme?: SystemTheme}
| undefined;
return staticData?.theme ?? theme;
},
undefined,
);
<ThemeProvider forcedTheme={forcedTheme}>{children}</ThemeProvider>;