# Advanced Usage Custom Themes [#custom-themes] You can define custom themes beyond light and dark: ```tsx {children} ``` Use the `value` prop to map theme names to different attribute values: ```tsx {children} ``` Multiple Attributes [#multiple-attributes] You can apply themes to multiple attributes simultaneously: ```tsx {children} ``` This is useful when you need both class-based styling (for Tailwind) and data attributes (for custom CSS). Forced Themes [#forced-themes] Force a specific theme for a page or section: ```tsx {children} ``` When `forcedTheme` is set, it overrides user preferences and localStorage. This is useful for: * Landing pages that should always be light * Documentation sections that should always be dark * A/B testing different themes Disable System Theme [#disable-system-theme] Disable system theme detection: ```tsx {children} ``` When `enableSystem` is false, the `"system"` option is removed from available themes. Storage Options [#storage-options] By default, Better Themes uses `localStorage` to persist theme preferences across all tabs. You can switch to `sessionStorage` for per-tab theme isolation: ```tsx {children} ``` localStorage vs sessionStorage [#localstorage-vs-sessionstorage] | Storage Type | Behavior | | ------------------------ | ---------------------- | | `localStorage` (default) | Shared across all tabs | | `sessionStorage` | Isolated per tab | When to use sessionStorage [#when-to-use-sessionstorage] Use `sessionStorage` when you need different themes in different tabs. For example, in a multi-user environment where multiple users are logged into different accounts in separate tabs, each user can have their own theme preference. Custom Storage Key [#custom-storage-key] Change the key used to store theme preferences: ```tsx {children} ``` Useful when you have multiple theme providers in the same app or want to avoid conflicts with other libraries. Disable Color Scheme [#disable-color-scheme] Disable the `color-scheme` CSS property: ```tsx {children} ``` The `color-scheme` property tells browsers which color scheme to use for native UI elements. You might want to disable it if you're handling this manually. Disable Transitions [#disable-transitions] Disable CSS transitions when switching themes: ```tsx {children} ``` This prevents flashy transitions when themes change. Better Themes temporarily disables all transitions, applies the theme, then re-enables them. Content Security Policy (CSP) [#content-security-policy-csp] If you're using CSP headers, provide a nonce for the inline script: ```tsx {children} ``` The nonce is passed to the inline script that prevents flash on load. Programmatic Theme Control [#programmatic-theme-control] Control themes programmatically: ```tsx twoslash import { useTheme } from "better-themes"; // ---cut--- function MyComponent() { const { theme, setTheme, themes, systemTheme } = useTheme(); // Set theme directly setTheme("dark"); // Set theme with callback setTheme((current) => (current === "dark" ? "light" : "dark")); // Access all available themes console.log(themes); // ["light", "dark", "system"] // Access system preference console.log(systemTheme); // "dark" or "light" } ``` Conditional Rendering [#conditional-rendering] Safely render theme-dependent content: ```tsx import { useTheme } from "better-themes"; import { useEffect, useState } from "react"; function ThemedContent() { const [mounted, setMounted] = useState(false); const { theme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return
Loading...
; } return (
{theme === "dark" && } {theme === "light" && }
); } ``` TypeScript [#typescript] Better Themes is fully typed. Import types for your own components: ```tsx import type { ThemeProviderProps, UseThemeProps } from "better-themes"; function CustomProvider(props: ThemeProviderProps) { // ... } function useCustomTheme(): UseThemeProps { // ... } ``` # API Reference ThemeProvider [#themeprovider] The main provider component that wraps your application. Props [#props] Default Values [#default-values] * `themes`: `["light", "dark"]` * `defaultTheme`: `"system"` (if `enableSystem` is true), otherwise `"light"` * `storage`: `"localStorage"` * `storageKey`: `"theme"` * `enableSystem`: `true` * `enableColorScheme`: `true` * `attribute`: `"class"` * `disableTransitionOnChange`: `false` Examples [#examples] **Basic usage:** ```tsx title="layout.tsx" {children} ``` **With custom themes:** ```tsx title="layout.tsx" {children} ``` **With data attribute:** ```tsx title="layout.tsx" {children} ``` **Disable transitions on change:** ```tsx title="layout.tsx" {children} ``` **Force a specific theme:** ```tsx title="layout.tsx" {children} ``` **Use sessionStorage for per-tab themes:** ```tsx title="layout.tsx" {children} ``` useTheme Hook [#usetheme-hook] Returns theme state and controls. Return Value [#return-value] ```tsx interface UseThemeProps { /** List of all available theme names */ themes: string[]; /** Forced theme name for the current page */ forcedTheme?: string; /** Update the theme */ setTheme: Dispatch>; /** Active theme name */ theme?: string; /** System theme preference ("dark" or "light") */ systemTheme?: "dark" | "light"; } ``` Properties [#properties] * `theme` - Current active theme name (`undefined` on server) * `setTheme` - Function to update theme (accepts string or callback) * `forcedTheme` - Forced theme if set, otherwise `undefined` * `themes` - Array of all available themes (includes `"system"` if enabled) * `systemTheme` - System preference (`"dark"` or `"light"`, only if `enableSystem` is true) Examples [#examples-1] **Basic usage:** ```tsx title="ThemeToggle.tsx" twoslash import { useTheme } from "better-themes"; // ---cut--- const { theme, setTheme } = useTheme(); setTheme("dark"); ``` **With callback:** ```tsx title="ThemeToggle.tsx" twoslash import { useTheme } from "better-themes"; // ---cut--- const { theme, setTheme } = useTheme(); setTheme((current) => (current === "dark" ? "light" : "dark")); ``` **Access system theme:** ```tsx title="ThemeToggle.tsx" twoslash import { useTheme } from "better-themes"; // ---cut--- const { systemTheme } = useTheme(); console.log(systemTheme); // "dark" or "light" ``` Types [#types] Attribute [#attribute] ```tsx type Attribute = `data-${string}` | "class"; ``` The HTML attribute type that can be modified. Can be `"class"` or any `data-*` attribute. ValueObject [#valueobject] ```tsx interface ValueObject { [themeName: string]: string; } ``` Mapping of theme names to HTML attribute values. Useful when you want different attribute values than theme names. Example [#example] ```tsx title="layout.tsx" {children} ``` RSC Export [#rsc-export] For Next.js App Router and other React Server Components frameworks, use the `/rsc` export: ```tsx title="layout.tsx" import { ThemeProvider, useTheme } from "better-themes/rsc"; ``` This ensures proper server component compatibility. The API is identical to the main export. # Getting Started Better Themes is a framework-agnostic theming solution for React applications. It provides seamless dark mode support with zero flash on load, system preference detection, and flexible storage options. Better Themes Preview Features [#features] * **Zero flash on load** - Prevents theme flash during page load (SSR/SSG safe) * **System preference detection** - Automatically detects and respects user's system theme preference via `prefers-color-scheme` * **Storage support** - Support for `localStorage` (default) and `sessionStorage` (per-tab isolation) * **Themed browser UI** - Sets `color-scheme` CSS property for native browser UI elements * **Custom themes** - Support for multiple custom themes beyond light/dark * **Flexible styling** - Use class or data attributes (works with Tailwind CSS) * **TypeScript** - Fully typed with TypeScript * **Framework agnostic** - Works with Next.js, Remix / React Router, Vite, TanStack Start, Waku, and more Quick Start [#quick-start] Install better-themes: npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Wrap your app with `ThemeProvider`: ```tsx title="layout.tsx" import { ThemeProvider } from "better-themes"; function Layout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Use the theme in your components: ```tsx title="theme-switcher.tsx" import { useTheme } from "better-themes"; function theme-switcher() { const { theme, setTheme } = useTheme(); return ( ); } ``` This project is deployed on Netlify Deploys by Netlify Next Steps [#next-steps] # Installation Package Managers [#package-managers] Install better-themes using your preferred package manager: npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Requirements [#requirements] * React 17.0.0 or higher (or React 18.0.0 or React 19.0.0) * React DOM 17.0.0 or higher (or React DOM 18.0.0 or React DOM 19.0.0) Peer Dependencies [#peer-dependencies] Better Themes has the following peer dependencies: * `react`: `^17.0.0 || ^18.0.0 || ^19.0.0` * `react-dom`: `^17.0.0 || ^18.0.0 || ^19.0.0` These are automatically installed by most package managers, but you may need to install them manually if you're using a different setup. Next Steps [#next-steps] After installation, check out the [Quick Start](/docs/quick-start) guide to get started with better-themes. # Quick Start Basic Setup [#basic-setup] Wrap your app with ThemeProvider [#wrap-your-app-with-themeprovider] The `ThemeProvider` component should be placed at the root of your application: ```tsx title="layout.tsx" import { ThemeProvider } from "better-themes"; function Layout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Add suppressHydrationWarning to your tag [#add-suppresshydrationwarning-to-your-html-tag] This prevents hydration warnings when the theme is applied: ```tsx title="layout.tsx" {children} ``` Style your app [#style-your-app] By default, `ThemeProvider` sets a `class` attribute on the `` element. Style your app with CSS: ```css title="global.css" :root { --background: white; --foreground: black; } .dark { --background: black; --foreground: white; } ``` Or use hard-coded values: ```css title="global.css" html, body { color: #000; background: #fff; } .dark, .dark body { color: #fff; background: #000; } ``` Using the Theme [#using-the-theme] Access the current theme and change it with the `useTheme` hook: ```tsx title="theme-switcher.tsx" import { useTheme } from "better-themes"; function theme-switcher() { const { theme, setTheme, themes } = useTheme(); return (

Current theme: {theme}

); } ``` Tailwind CSS [#tailwind-css] For Tailwind CSS, use class-based dark mode: ```tsx title="layout.tsx" {children} ``` Then use Tailwind's dark mode classes: ```tsx title="page.tsx"

Hello

``` Important Notes [#important-notes] * The theme value is `undefined` on the server. Only use it after the component mounts on the client. * See the [SSR Guide](/docs/ssr) for details on handling server-side rendering. * Check out [Framework Guides](/docs/frameworks/next-app) for framework-specific setup instructions. # SSR & Hydration Understanding SSR Behavior [#understanding-ssr-behavior] Better Themes is designed to work seamlessly with server-side rendering. The theme value is `undefined` on the server to prevent hydration mismatches. The Problem [#the-problem] When using SSR, the server doesn't know the user's theme preference. If you render theme-dependent UI on the server, it may not match what's rendered on the client, causing hydration errors. The Solution [#the-solution] Only render theme-dependent UI after the component mounts on the client: ```tsx import { useEffect, useState } from "react"; import { useTheme } from "better-themes"; function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme, themes } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return
Loading...
; // or a skeleton } return ( ); } ``` Best Practices [#best-practices] 1. Use a mounted state [#1-use-a-mounted-state] Always check if the component has mounted before rendering theme-dependent UI: ```tsx const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; // or a loading state } ``` 2. Provide fallback UI [#2-provide-fallback-ui] Show a neutral loading state or skeleton while waiting for hydration: ```tsx if (!mounted) { return ; } ``` 3. Use CSS for initial styles [#3-use-css-for-initial-styles] Use CSS to handle initial theme styles, not JavaScript: ```css :root { background: white; color: black; } [data-theme='dark'] { background: black; color: white; } ``` 4. Avoid theme checks in render [#4-avoid-theme-checks-in-render] Don't conditionally render based on theme before mounting: ```tsx // Bad - theme is undefined on server {theme === "dark" && } // Good - check mounted first {mounted && theme === "dark" && } ``` Zero Flash Strategy [#zero-flash-strategy] Better Themes prevents flash by injecting a script before React hydrates. This script: 1. Reads the theme from localStorage 2. Applies the theme immediately 3. Prevents any flash of unstyled content The script runs synchronously before the page renders, ensuring zero flash. Framework-Specific Notes [#framework-specific-notes] Next.js App Router [#nextjs-app-router] Use the `/rsc` export for proper server component support: ```tsx twoslash import { ThemeProvider } from "better-themes/rsc"; ``` Next.js Pages Router [#nextjs-pages-router] Use the default export: ```tsx import { ThemeProvider } from "better-themes"; ``` Other Frameworks [#other-frameworks] All frameworks use the default export. The SSR behavior is consistent across all React frameworks. # Theme Switchers Here are some examples of beautiful theme switchers you can use in your application. We provide implementation using **Shadcn UI** and a **Custom** implementation without external UI libraries. Shadcn UI [#shadcn-ui] These examples require `lucide-react` icons and Shadcn UI components. Radio Group Switcher [#radio-group-switcher] Uses `RadioGroup` and `Label` components. Component [#component] ```tsx title="components/radio-switcher.tsx" "use client"; import { Monitor, Moon, Sun } from "lucide-react"; import { useTheme } from "better-themes"; import { useEffect, useState } from "react"; import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { cn } from "@/lib/utils"; const themeOptions = [ { value: "dark", icon: Moon, label: "Dark" }, { value: "light", icon: Sun, label: "Light" }, { value: "system", icon: Monitor, label: "System" }, ]; export function RadioSwitcher() { const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); return ( setTheme(value) : undefined} className="flex items-center gap-2" >
{themeOptions.map(({ value, icon: Icon, label }) => ( ))}
); } ```
Button Toggle [#button-toggle] A compact toggle button using the `Button` component. ```tsx title="components/button-toggle.tsx" "use client"; import { Moon, Sun } from "lucide-react"; import { useTheme } from "better-themes"; import { Button } from "@/components/ui/button"; export function ButtonToggle() { const { theme, setTheme } = useTheme(); return ( ); } ``` Custom Implementation [#custom-implementation] These implementations use standard HTML elements and Tailwind CSS, requiring no external UI libraries besides `lucide-react` for icons. Custom Radio Switcher [#custom-radio-switcher] ```tsx title="components/custom-radio-switcher.tsx" "use client"; import { Monitor, Moon, Sun } from "lucide-react"; import { useTheme } from "better-themes"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; const themeOptions = [ { value: "dark", icon: Moon, label: "Dark" }, { value: "light", icon: Sun, label: "Light" }, { value: "system", icon: Monitor, label: "System" }, ]; export function CustomThemeSwitcher() { const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return (
{themeOptions.map(({ value, icon: Icon, label }) => ( ))}
); } ``` Simple Toggle [#simple-toggle] A simpler toggle button that switches between light and dark mode. ```tsx title="components/simple-toggle.tsx" "use client"; import { Moon, Sun } from "lucide-react"; import { useTheme } from "better-themes"; import { useEffect, useState } from "react"; export function SimpleToggle() { const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Examples [#examples] Full source code: [Better Themes Examples](https://github.com/SaviruFr/better-themes/tree/main/examples/theme-switchers) # Fumadocs Installation [#installation] npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Setup [#setup] Update your root layout [#update-your-root-layout] In `app/layout.tsx`, wrap your app with `ThemeProvider` and disable Fumadocs' built-in theme: ```tsx title="app/layout.tsx" import { RootProvider } from "fumadocs-ui/provider/next"; import { ThemeProvider } from "better-themes/rsc"; import type { ReactNode } from "react"; export default function Layout({ children }: { children: ReactNode }) { return ( {children} ); } ``` **Important:** * Use the `/rsc` export for Fumadocs (Next.js App Router) * Set `theme={{ enabled: false }}` on `RootProvider` to disable Fumadocs' built-in theme system * Add `suppressHydrationWarning` to your `` tag Create a theme switcher component [#create-a-theme-switcher-component] Create a client component for theme switching: ```tsx title="components/theme-switcher.tsx" "use client"; import { useTheme } from "better-themes/rsc"; import { useEffect, useState } from "react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Add theme switcher to layouts [#add-theme-switcher-to-layouts] Add the theme switcher to both `HomeLayout` and `DocsLayout`: **Home Layout** (`app/(home)/layout.tsx`): ```tsx title="app/(home)/layout.tsx" import { HomeLayout } from "fumadocs-ui/layouts/home"; import { ThemeSwitcher } from "@/components/theme-switcher"; import type { ReactNode } from "react"; export default function Layout({ children }: { children: ReactNode }) { return ( }}> {children} ); } ``` **Docs Layout** (`app/docs/layout.tsx`): ```tsx title="app/docs/layout.tsx" import { DocsLayout } from "fumadocs-ui/layouts/docs"; import { source } from "@/lib/source"; import { ThemeSwitcher } from "@/components/theme-switcher"; import type { ReactNode } from "react"; export default function Layout({ children }: { children: ReactNode }) { return ( }} tree={source.pageTree} > {children} ); } ``` Complete Example [#complete-example] ```tsx title="app/layout.tsx" import { RootProvider } from "fumadocs-ui/provider/next"; import { ThemeProvider } from "better-themes/rsc"; export default function Layout({ children }: LayoutProps<"/">) { return ( {children} ); } ``` ```tsx title="app/(home)/layout.tsx" import { HomeLayout } from "fumadocs-ui/layouts/home"; import { ThemeSwitcher } from "@/components/theme-switcher"; export default function Layout({ children }: LayoutProps<"/">) { return ( }}> {children} ); } ``` ```tsx title="app/docs/layout.tsx" import { DocsLayout } from "fumadocs-ui/layouts/docs"; import { source } from "@/lib/source"; import { ThemeSwitcher } from "@/components/theme-switcher"; export default function Layout({ children }: LayoutProps<"/docs">) { return ( }} tree={source.pageTree} > {children} ); } ``` ```tsx title="components/theme-switcher.tsx" "use client"; import { useTheme } from "better-themes/rsc"; import { useEffect, useState } from "react"; import { Monitor, Moon, Sun } from "lucide-react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return (
); } ``` Notes [#notes] * Always use `better-themes/rsc` for Fumadocs (Next.js App Router) * Disable Fumadocs' built-in theme by setting `theme={{ enabled: false }}` on `RootProvider` * Remember to add `suppressHydrationWarning` to the `` tag * The theme switcher can be integrated into both `HomeLayout` and `DocsLayout` using the `themeSwitch` prop Example [#example] View a complete example implementation: [Fumadocs Example](https://github.com/SaviruFr/better-themes/tree/main/examples/fumadocs) Try the live demo: [Live Demo](https://demo-better-themes.netlify.app/) # Next.js App Router Installation [#installation] npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Setup [#setup] Create or update your root layout [#create-or-update-your-root-layout] In `app/layout.tsx`, wrap your app with `ThemeProvider`: ```tsx title="app/layout.tsx" import { ThemeProvider } from "better-themes/rsc"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` **Important:** Use the `/rsc` export for Next.js App Router to ensure proper server component compatibility. Add suppressHydrationWarning [#add-suppresshydrationwarning] Add `suppressHydrationWarning` to your `` tag to prevent hydration warnings. Create a theme switcher component [#create-a-theme-switcher-component] Create a client component for theme switching: ```tsx title="components/theme-switcher.tsx" "use client"; import { useTheme } from "better-themes/rsc"; import { useEffect, useState } from "react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Tailwind CSS Configuration [#tailwind-css-configuration] If you're using Tailwind CSS, configure it for class-based dark mode: ```js title="tailwind.config.js" export default { darkMode: ["class"], // ... rest of config }; ``` Complete Example [#complete-example] ```tsx title="app/layout.tsx" import { ThemeProvider } from "better-themes/rsc"; import { ThemeSwitcher } from "@/components/theme-switcher"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ```tsx title="components/theme-switcher.tsx" "use client"; import { useTheme } from "better-themes/rsc"; import { useEffect, useState } from "react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Notes [#notes] * Always use `better-themes/rsc` for Next.js App Router * Remember to add `suppressHydrationWarning` to the `` tag * See the [SSR Guide](/docs/ssr) for more details on handling server-side rendering Example [#example] View a complete example implementation: [Next.js App Router Example](https://github.com/SaviruFr/better-themes/tree/main/examples/next-app) Try the live demo: [Live Demo](https://demo-better-themes.netlify.app/) # Next.js Pages Router Installation [#installation] npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Setup [#setup] Update _app.tsx [#update-_apptsx] Wrap your app with `ThemeProvider` in `pages/_app.tsx`: ```tsx title="pages/_app.tsx" import { ThemeProvider } from "better-themes"; import type { AppProps } from "next/app"; export default function App({ Component, pageProps }: AppProps) { return ( ); } ``` Update _document.tsx [#update-_documenttsx] Add `suppressHydrationWarning` to your `` tag: ```tsx title="pages/_document.tsx" import { Html, Head, Main, NextScript } from "next/document"; export default function Document() { return (
); } ``` Create a theme switcher component [#create-a-theme-switcher-component] ```tsx title="components/theme-switcher.tsx" import { useTheme } from "better-themes"; import { useEffect, useState } from "react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Tailwind CSS Configuration [#tailwind-css-configuration] If you're using Tailwind CSS, configure it for class-based dark mode: ```js title="tailwind.config.js" export default { darkMode: ["class"], // ... rest of config }; ``` Complete Example [#complete-example] ```tsx title="pages/_app.tsx" import { ThemeProvider } from "better-themes"; import type { AppProps } from "next/app"; import { ThemeSwitcher } from "@/components/theme-switcher"; export default function App({ Component, pageProps }: AppProps) { return ( ); } ``` ```tsx title="pages/_document.tsx" import { Html, Head, Main, NextScript } from "next/document"; export default function Document() { return (
); } ``` Notes [#notes] * Use the default export (not `/rsc`) for Next.js Pages Router * Add `suppressHydrationWarning` to the `` component in `_document.tsx` Example [#example] View a complete example implementation: [Next.js Pages Router Example](https://github.com/SaviruFr/better-themes/tree/main/examples/next-pages) Try the live demo: [Live Demo](https://demo-better-themes.netlify.app/) # Remix / React Router v7 Installation [#installation] npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Setup [#setup] Create or update your root layout [#create-or-update-your-root-layout] In `app/root.tsx`, wrap your app with `ThemeProvider`: ```tsx title="app/root.tsx" import { Links, Meta, Outlet, Scripts, ScrollRestoration, } from "react-router"; // or "@remix-run/react" import { ThemeProvider } from "better-themes"; export function Layout({ children }: { children: React.ReactNode }) { return ( {children} ); } export default function App() { return ; } ``` Add suppressHydrationWarning [#add-suppresshydrationwarning] Add `suppressHydrationWarning` to your `` tag in `app/root.tsx` to prevent hydration warnings. Create a theme switcher component [#create-a-theme-switcher-component] Create a component for theme switching: ```tsx title="app/components/theme-switcher.tsx" import { useTheme } from "better-themes"; import { useEffect, useState } from "react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Tailwind CSS Configuration [#tailwind-css-configuration] If you're using Tailwind CSS, configure it for class-based dark mode: ```js title="tailwind.config.js" export default { darkMode: ["class"], // ... rest of config }; ``` Complete Example [#complete-example] ```tsx title="app/root.tsx" import { Links, Meta, Outlet, Scripts, ScrollRestoration, } from "react-router"; import { ThemeProvider } from "better-themes"; import { ThemeSwitcher } from "./components/theme-switcher"; export function Layout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Example [#example] View a complete example implementation: [Remix Example](https://github.com/SaviruFr/better-themes/tree/main/examples/remix) Try the live demo: [Live Demo](https://demo-better-themes.netlify.app/) # TanStack Start Installation [#installation] npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Setup [#setup] Update your root route [#update-your-root-route] In your root route file (typically `routes/__root.tsx`), wrap your app with `ThemeProvider`: ```tsx title="routes/__root.tsx" import { createRootRoute } from "@tanstack/react-router"; import { ThemeProvider } from "better-themes"; import { HeadContent, Scripts } from "@tanstack/react-router"; function RootDocument({ children }: { children: React.ReactNode }) { return ( {children} ); } export const Route = createRootRoute({ component: RootDocument, }); ``` Create a theme switcher component [#create-a-theme-switcher-component] ```tsx title="components/theme-switcher.tsx" import { useTheme } from "better-themes"; import { useHydrated } from "@tanstack/react-router"; export function ThemeSwitcher() { const hydrated = useHydrated(); const { theme, setTheme } = useTheme(); return ( ); } ``` Complete Example [#complete-example] ```tsx title="routes/__root.tsx" import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router"; import { ThemeProvider } from "better-themes"; import { ThemeSwitcher } from "@/components/theme-switcher"; function RootDocument({ children }: { children: React.ReactNode }) { return ( {children} ); } export const Route = createRootRoute({ component: RootDocument, }); ``` Notes [#notes] * Use the default export for TanStack Start * Add `suppressHydrationWarning` to the `` tag Example [#example] View a complete example implementation: [TanStack Start Example](https://github.com/SaviruFr/better-themes/tree/main/examples/tanstack) Try the live demo: [Live Demo](https://demo-better-themes.netlify.app/) # Vite Installation [#installation] npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Setup [#setup] Update your root component [#update-your-root-component] In your main entry file (typically `main.tsx` or `App.tsx`), wrap your app with `ThemeProvider`: ```tsx title="main.tsx" import { ThemeProvider } from "better-themes"; import { createRoot } from "react-dom/client"; import App from "./App"; import "./global.css"; createRoot(document.getElementById("root")!).render( ); ``` Style your app [#style-your-app] ```css title="global.css" :root { --background: white; --foreground: black; } .dark { --background: black; --foreground: white; } body { background: var(--background); color: var(--foreground); } ``` Create a theme switcher component [#create-a-theme-switcher-component] ```tsx title="components/ThemeSwitcher.tsx" import { useTheme } from "better-themes"; import { useEffect, useState } from "react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Complete Example [#complete-example] ```tsx title="main.tsx" import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import "./global.css"; import App from "./App.tsx"; import { ThemeProvider } from "better-themes"; createRoot(document.getElementById("root")!).render( ); ``` ```tsx title="components/ThemeSwitcher.tsx" import { useTheme } from "better-themes"; import { useEffect, useState } from "react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Notes [#notes] * Use the default export for Vite * If using Vite with SSR, add `suppressHydrationWarning` to the `` tag * Works with both Vite and Create React App Example [#example] View a complete example implementation: [Vite Example](https://github.com/SaviruFr/better-themes/tree/main/examples/vite) Try the live demo: [Live Demo](https://demo-better-themes.netlify.app/) # Waku Installation [#installation] npm pnpm yarn bun ```bash npm install better-themes ``` ```bash pnpm add better-themes ``` ```bash yarn add better-themes ``` ```bash bun add better-themes ``` Setup [#setup] Update your root layout [#update-your-root-layout] In `pages/_layout.tsx`, wrap your app with `ThemeProvider`: ```tsx title="pages/_layout.tsx" import { ThemeProvider } from "better-themes/rsc"; import type { ReactNode } from "react"; type RootLayoutProps = { children: ReactNode }; export default async function RootLayout({ children }: RootLayoutProps) { return ( {/* Your head content */} {children} ); } ``` **Note:** Waku uses React Server Components, so use the `/rsc` export. Create a theme switcher component [#create-a-theme-switcher-component] ```tsx title="components/theme-switcher.tsx" "use client"; import { useTheme } from "better-themes/rsc"; import { useEffect, useState } from "react"; export function ThemeSwitcher() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => { setMounted(true); }, []); if (!mounted) { return null; } return ( ); } ``` Complete Example [#complete-example] ```tsx title="pages/_layout.tsx" import { ThemeProvider } from "better-themes/rsc"; import type { ReactNode } from "react"; import { ThemeSwitcher } from "@/components/theme-switcher"; type RootLayoutProps = { children: ReactNode }; export default async function RootLayout({ children }: RootLayoutProps) { return ( {children} ); } ``` Notes [#notes] * Use `better-themes/rsc` for Waku (React Server Components) * Add `suppressHydrationWarning` to the `` tag Example [#example] View a complete example implementation: [Waku Example](https://github.com/SaviruFr/better-themes/tree/main/examples/waku) Try the live demo: [Live Demo](https://demo-better-themes.netlify.app/)