Theme
ThemeProvider setup, useTheme hook, ThemeToggle component, and dark mode configuration.
Overview
The theming system from packages/ui provides dark mode support across the application. It includes a ThemeProvider for managing the current theme, a useTheme hook for programmatic access, and a ThemeToggle button component.
Setup
Wrap your application with ThemeProvider in the root layout:
// app/layout.tsx
import { ThemeProvider } from "@saas/ui";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}Provider Props
| Prop | Type | Default | Description |
|---|---|---|---|
attribute | "class" | "data-theme" | "class" | How the theme is applied to <html> |
defaultTheme | "light" | "dark" | "system" | "system" | Initial theme |
enableSystem | boolean | true | Respect OS-level preference |
disableTransitionOnChange | boolean | false | Prevent flash during theme switch |
The suppressHydrationWarning on <html> is required to avoid React hydration mismatch warnings since the theme class is set before React hydrates.
ThemeToggle Component
Drop the toggle button anywhere in your UI (typically in the header or settings):
import { ThemeToggle } from "@saas/ui";
<ThemeToggle />The toggle cycles between light and dark modes, displaying a sun or moon icon accordingly.
useTheme Hook
Access the current theme programmatically:
"use client";
import { useTheme } from "@saas/ui";
export default function MyComponent() {
const { theme, setTheme, resolvedTheme } = useTheme();
return (
<div>
<p>Current theme: {resolvedTheme}</p>
<button onClick={() => setTheme("light")}>Light</button>
<button onClick={() => setTheme("dark")}>Dark</button>
<button onClick={() => setTheme("system")}>System</button>
</div>
);
}Hook Return Values
| Property | Type | Description |
|---|---|---|
theme | string | The selected theme ("light", "dark", or "system") |
setTheme | (theme: string) => void | Function to change the theme |
resolvedTheme | string | The actual applied theme (resolves "system" to "light" or "dark") |
Dark Mode with Tailwind
Tailwind dark mode is configured with the class strategy in tailwind.config.ts:
// tailwind.config.ts
export default {
darkMode: "class",
// ...
};Use the dark: prefix in your components:
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
Content adapts to theme
</div>Common Patterns
Conditional rendering based on theme
const { resolvedTheme } = useTheme();
<img
src={resolvedTheme === "dark" ? "/logo-white.svg" : "/logo-dark.svg"}
alt="Logo"
/>Persisting theme preference
The ThemeProvider automatically saves the user's preference to localStorage and restores it on subsequent visits.
Tips
- Always use
resolvedThemeinstead ofthemewhen checking the active mode, sincethemecan be"system". - Test dark mode on every page before shipping. Common issues include hardcoded colors (e.g.,
text-gray-900without adark:counterpart). - Use CSS custom properties for brand colors that change between themes.
Done reading? Mark this page as complete.