Theming
Class-based dark mode with a no-flash inline script, system preference support, and a ready-made toggle.
Try it
Current theme
Preference: system · Resolved: light
Setup
Wrap your app once. ThemeScript applies the stored theme before first paint so there is no flash of the wrong mode.
// app/layout.tsx
import { ThemeProvider, ThemeScript } from '@tra/ui';
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeScript />
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}Reading and setting the theme
'use client';
import { useTheme, ThemeToggle } from '@tra/ui';
function Settings() {
const { theme, resolvedTheme, setTheme } = useTheme();
return (
<>
<ThemeToggle /> {/* or roll your own: */}
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('system')}>Match system</button>
</>
);
}How it works
- Dark mode is the
.darkclass on<html>— every semantic token swaps automatically. - The preference persists in
localStorageundertra-theme. systemtracks the OS and updates live when the OS preference changes.- Components built with semantic tokens (
bg-canvas,text-ink,border-line) never needdark:overrides.