# How to Implement Dark/Light Theme Switching with the ModeToggle Component in refine-shadcn

> Easily implement dark/light theme switching in your refine-shadcn app. Wrap with ThemeProvider and add ModeToggle for instant theme changes with persisted preferences.

- Repository: [Ferdi ÜNAL/refine-shadcn](https://github.com/ferdiunal/refine-shadcn)
- Tags: how-to-guide
- Published: 2026-03-01

---

**Wrap your application with the `ThemeProvider` from `@ferdiunal/refine-shadcn` and place the `ModeToggle` component in your header to enable instant theme switching with persisted user preferences.**

The `refine-shadcn` library provides a production-ready theme switching solution built on top of `next-themes` and Tailwind CSS. By combining the `ThemeProvider` context wrapper with the pre-built `ModeToggle` UI component, you can implement a complete dark/light mode system that respects OS-level preferences and automatically persists user choices across sessions.

## How the ModeToggle Architecture Works

The theme system in `refine-shadcn` relies on three integrated pieces that coordinate through React context and CSS classes.

**ThemeProvider** ([`packages/theme/src/providers/theme-provider.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/providers/theme-provider.tsx)) is a thin wrapper around `next-themes` that supplies the `theme` context containing `setTheme`, `theme`, and `systemTheme` values. It handles injecting the `dark` class onto the `<html>` element and persists the selected mode to `localStorage`.

**ModeToggle** ([`packages/theme/src/components/modeToggle.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/modeToggle.tsx)) is a shadcn-based dropdown component that renders sun and moon icons with Tailwind transition animations. It consumes the `useTheme()` hook to call `setTheme('light' | 'dark' | 'system')` when users select an option from the menu.

**Tailwind CSS** processes the `dark` class via the `darkMode: "class"` configuration, enabling all `dark:` utility variants to respond instantly when the class changes.

At runtime, the flow works as follows: `ThemeProvider` initializes the theme from storage or system preference, `ModeToggle` presents the UI controls, and selecting an option triggers `next-themes` to update the DOM and storage state.

## Step-by-Step Implementation Guide

### Install the Package and Import Global Styles

First, add the library to your project and import the required CSS that includes Tailwind directives and shadcn base styles.

```bash
pnpm add @ferdiunal/refine-shadcn

```

In your entry file (e.g., [`src/main.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/src/main.tsx)), import the global stylesheet before rendering your app:

```tsx
import "@ferdiunal/refine-shadcn/dist/globals.css";

```

### Configure the ThemeProvider Wrapper

Import `ThemeProvider` from the package and wrap your application root. The provider must be rendered outside any layout components that use the toggle.

```tsx
// src/App.tsx
import { ThemeProvider } from "@ferdiunal/refine-shadcn";

function App() {
  return (
    <ThemeProvider
      attribute="class"
      defaultTheme="system"
      enableSystem
    >
      {/* Your Refine and Router configuration */}
    </ThemeProvider>
  );
}

```

The `attribute="class"` prop instructs `next-themes` to toggle the `dark` class on the `<html>` element, which activates Tailwind's dark mode utilities. Setting `defaultTheme="system"` with `enableSystem` ensures the app respects the user's OS preference on first load.

### Add the ModeToggle to Your Layout

You can place the toggle anywhere in your UI. The `DefaultLayout` provided by the library already includes it in the header at lines 198-207 of [`packages/theme/src/layouts/default.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/layouts/default.tsx):

```tsx
// Excerpt from packages/theme/src/layouts/default.tsx
<header className="flex items-center justify-between px-4 py-2">
  {/* Logo and navigation */}
  <ModeToggle />
</header>

```

For custom layouts, import and render the component directly:

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

function CustomHeader() {
  return (
    <header className="flex items-center justify-end p-4 gap-4">
      <h1>Dashboard</h1>
      <ModeToggle />
    </header>
  );
}

```

When clicked, the toggle displays a dropdown menu with **Light**, **Dark**, and **System** options. Selecting an option immediately updates the UI theme and saves the preference.

## Customizing the Theme Toggle Behavior

The `ModeToggle` component is a lightweight wrapper around the shadcn `DropdownMenu`. If you need additional options—such as "High Contrast" mode or custom icons—you can copy the source from [`packages/theme/src/components/modeToggle.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/modeToggle.tsx) into your project, modify the `DropdownMenuItem` entries, and use your custom version instead.

For programmatic control outside of the toggle button, access the same `useTheme` hook that `ModeToggle` uses:

```tsx
import { useTheme } from "@ferdiunal/refine-shadcn";

function ThemeAwareComponent() {
  const { theme, setTheme } = useTheme();
  
  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      Current: {theme}
    </button>
  );
}

```

## Troubleshooting Common Issues

- **No visual change after selecting a theme**: Ensure `ThemeProvider` is the outermost component in your tree and that you imported [`globals.css`](https://github.com/ferdiunal/refine-shadcn/blob/main/globals.css). Without the CSS import, Tailwind cannot process the `dark:` variants.
- **Icons appear static without animation**: Verify that your [`tailwind.config.js`](https://github.com/ferdiunal/refine-shadcn/blob/main/tailwind.config.js) includes `darkMode: "class"`. The `refine-shadcn` templates pre-configure this, but custom setups may override it.
- **Theme resets on page refresh**: This occurs when `localStorage` is disabled (e.g., strict incognito modes). The library falls back to the system theme when persistence is unavailable.
- **Dropdown menu does not open**: Check that your build system resolves the `@/ui/dropdown-menu` alias correctly. The library's [`tsconfig.json`](https://github.com/ferdiunal/refine-shadcn/blob/main/tsconfig.json) sets this path, but custom Vite or Webpack configs must match.

## Summary

- **Wrap your app** with `ThemeProvider` from `@ferdiunal/refine-shadcn` using `attribute="class"` and `enableSystem` to activate the theme context.
- **Import global styles** via `@ferdiunal/refine-shadcn/dist/globals.css` to enable Tailwind dark mode utilities.
- **Place `ModeToggle`** in your header or settings panel; the component is pre-built and ready to use.
- **Persistence is automatic**—`next-themes` handles `localStorage` and system preference detection without additional configuration.

## Frequently Asked Questions

### Where is the ModeToggle component located in the refine-shadcn source code?

The component is defined in [`packages/theme/src/components/modeToggle.tsx`](https://github.com/ferdiunal/refine-shadcn/blob/main/packages/theme/src/components/modeToggle.tsx). It imports `useTheme` from the provider and uses the shadcn `DropdownMenu`, `SunIcon`, and `MoonIcon` components to render the switching interface.

### Can I use the ThemeProvider without rendering the ModeToggle?

Yes. `ThemeProvider` establishes the context that any component can consume via the `useTheme` hook. You can build custom theme controls or programmatically call `setTheme()` without including the `ModeToggle` dropdown UI.

### Why does my application flash the light theme before switching to dark on reload?

This occurs when the theme script has not loaded before the first paint. Ensure `ThemeProvider` is rendered at the root level of your application (above any Suspense boundaries or layout effects) so the `next-themes` script can inject the correct class during the initial render cycle.

### How do I set a specific theme as the default instead of system preference?

Change the `defaultTheme` prop on `ThemeProvider` from `"system"` to `"light"` or `"dark"`. Remove the `enableSystem` prop if you want to disable system detection entirely and force users to manually select their preferred mode.