How to Configure the ThemeProvider with next-themes for Dark Mode in Refine-ShadCN

To enable dark mode in Refine-ShadCN, wrap your application with the ThemeProvider exported from @ferdiunal/refine-shadcn, set defaultTheme="dark", and keep enableSystem={true} to respect the user's OS preference.

Refine-ShadCN (ferdiunal/refine-shadcn) ships a thin wrapper around the next-themes library that handles all theme state management. Because the ThemeProvider component in packages/theme/src/providers/theme-provider.tsx simply forwards every prop to the underlying NextThemesProvider, you configure dark mode using the standard next-themes API.

How the ThemeProvider Works

The ThemeProvider is a client-side wrapper that re-exports next-themes without modification. Located at packages/theme/src/providers/theme-provider.tsx, it accepts the complete ThemeProviderProps interface from next-themes and passes them directly to the provider:

// packages/theme/src/providers/theme-provider.tsx
"use client";

import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
    return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

This architecture means any configuration valid for next-themes is valid for Refine-ShadCN's wrapper.

Configuring Dark Mode in Your Layout

For a Next.js App Router application, import the provider in your root layout and configure the dark mode defaults. The following example initializes the app in dark mode while still allowing system preference detection:

// app/layout.tsx
import "./globals.css";
import { ThemeProvider } from "@ferdiunal/refine-shadcn";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="dark"
          enableSystem={true}
          disableTransitionOnChange={false}
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

Set attribute="class" to apply themes via CSS classes (required for Tailwind's dark mode strategy), and defaultTheme="dark" to render the dark theme on first load before hydration.

Key Configuration Properties

The ThemeProvider accepts all standard next-themes properties. For dark mode configuration, these are the most critical:

  • defaultTheme: Sets the initial theme value. Use "dark" for dark-first applications, "system" to follow the OS, or "light" for light-first.
  • enableSystem: Boolean that allows the operating system preference to override defaultTheme. Keep this true to respect user system settings.
  • attribute: The HTML attribute to apply the theme class. Use "class" (default) for Tailwind CSS integration or "data-theme" for CSS variable scoping.
  • disableTransitionOnChange: Set to true to disable CSS transitions during theme switches, preventing color flash during hydration.
  • forcedTheme: Optional string to force a specific theme regardless of user preference, useful for embedded previews or marketing pages.

Using BaseLayout for Automatic Provider Injection

If you prefer a batteries-included approach, import the BaseLayout component from packages/theme/src/layouts/base.tsx. This layout automatically wraps children with ThemeProvider and includes additional UI wrappers like TooltipProvider and Toaster.

import { BaseLayout } from "@ferdiunal/refine-shadcn";

export default function Layout({ children }) {
  return (
    <BaseLayout
      defaultTheme="dark"
      enableSystem={true}
      attribute="class"
    >
      {children}
    </BaseLayout>
  );
}

The BaseLayout forwards all theme-related props to the underlying ThemeProvider, so the configuration API remains identical.

Adding User Controls with ModeToggle

Refine-ShadCN includes a pre-built toggle component located at packages/theme/src/components/modeToggle.tsx. This component renders a dropdown menu allowing users to switch between light, dark, and system modes:

import { ModeToggle } from "@ferdiunal/refine-shadcn";

export function Header() {
  return (
    <header className="flex justify-end p-4">
      <ModeToggle />
    </header>
  );
}

The ModeToggle uses the useTheme hook from next-themes internally to read and update the current theme state.

Controlling Themes Programmatically

For custom UI elements or settings pages, access the theme state directly using the useTheme hook from next-themes:

import { useTheme } from "next-themes";

function ThemeSettings() {
  const { theme, setTheme, resolvedTheme } = useTheme();

  const enableDarkMode = () => setTheme("dark");
  const enableLightMode = () => setTheme("light");
  const resetToSystem = () => setTheme("system");

  return (
    <div>
      <p>Current theme: {resolvedTheme}</p>
      <button onClick={enableDarkMode}>Force Dark</button>
      <button onClick={enableLightMode}>Force Light</button>
    </div>
  );
}

The resolvedTheme property returns the actual applied theme (resolving "system" to either "light" or "dark"), while theme returns the raw stored value.

Summary

Frequently Asked Questions

Does Refine-ShadCN use a custom theming engine or next-themes?

Refine-ShadCN uses a thin wrapper around next-themes. The ThemeProvider in packages/theme/src/providers/theme-provider.tsx simply re-exports NextThemesProvider and forwards all props unchanged, so the configuration API is identical to the standard next-themes library.

How do I prevent the white flash during page load in dark mode?

Set disableTransitionOnChange={true} on the ThemeProvider. This disables CSS transitions during the theme switch that happens on hydration, preventing the brief flash of unstyled content. Ensure your CSS framework (like Tailwind) is configured to use class-based dark mode with the "class" attribute.

Can I force a specific theme on certain pages while keeping global settings elsewhere?

Yes. Pass the forcedTheme prop to the ThemeProvider or BaseLayout on specific layouts or pages. This overrides both the defaultTheme and user preferences for that specific component tree, which is useful for landing pages or embedded dashboards that must display in a fixed color scheme.

Where is the user's theme preference stored?

By default, next-themes persists the selected theme in localStorage under the key theme. You can customize the storage key using the storageKey prop on the ThemeProvider, or disable persistence by handling theme state externally and passing controlled props.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →