ScaleRocket/Web

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

PropTypeDefaultDescription
attribute"class" | "data-theme""class"How the theme is applied to <html>
defaultTheme"light" | "dark" | "system""system"Initial theme
enableSystembooleantrueRespect OS-level preference
disableTransitionOnChangebooleanfalsePrevent 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

PropertyTypeDescription
themestringThe selected theme ("light", "dark", or "system")
setTheme(theme: string) => voidFunction to change the theme
resolvedThemestringThe 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 resolvedTheme instead of theme when checking the active mode, since theme can be "system".
  • Test dark mode on every page before shipping. Common issues include hardcoded colors (e.g., text-gray-900 without a dark: counterpart).
  • Use CSS custom properties for brand colors that change between themes.

Done reading? Mark this page as complete.

On this page