Skip to main content
Shadcn/ui provides built-in support for dark mode through CSS variables. This guide covers implementation for different frameworks.

How it Works

Dark mode in shadcn/ui works by:
  1. Adding a .dark class to the root element
  2. Applying different CSS variable values under the .dark selector
  3. Components automatically adapt based on the active theme
:root {
  --background: oklch(1 0 0); /* Light mode */
}

.dark {
  --background: oklch(0.145 0 0); /* Dark mode */
}

Framework Integration

Next.js

For Next.js applications, use the next-themes package.
1
Install next-themes
2
npm install next-themes
3
Create a theme provider
4
"use client"

import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"

export function ThemeProvider({
  children,
  ...props
}: React.ComponentProps<typeof NextThemesProvider>) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
5
Wrap your root layout
6
Add the ThemeProvider to your root layout and add the suppressHydrationWarning prop to the html tag.
7
import { ThemeProvider } from "@/components/theme-provider"

export default function RootLayout({ children }: RootLayoutProps) {
  return (
    <>
      <html lang="en" suppressHydrationWarning>
        <head />
        <body>
          <ThemeProvider
            attribute="class"
            defaultTheme="system"
            enableSystem
            disableTransitionOnChange
          >
            {children}
          </ThemeProvider>
        </body>
      </html>
    </>
  )
}
8
Add a mode toggle
9
Create a component to toggle between light and dark mode:
10
"use client"

import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function ModeToggle() {
  const { setTheme } = useTheme()

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="icon">
          <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
          <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
          <span className="sr-only">Toggle theme</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onClick={() => setTheme("light")}>
          Light
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("dark")}>
          Dark
        </DropdownMenuItem>
        <DropdownMenuItem onClick={() => setTheme("system")}>
          System
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

Customizing Dark Mode Colors

Customize dark mode colors by modifying the .dark selector in your CSS:
app/globals.css
.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  /* Customize other colors */
}
All color variables in the .dark selector override the light mode values when dark mode is active.

System Preference

Most theme providers support automatic detection of the user’s system preference:
<ThemeProvider defaultTheme="system" enableSystem>
  {children}
</ThemeProvider>
This will automatically apply dark mode when the user’s OS is set to dark mode.

Preventing Flash of Wrong Theme

Always include the theme initialization script before any content renders to prevent a flash of the wrong theme.
For Next.js:
  • Use suppressHydrationWarning on the <html> tag
  • The next-themes package handles this automatically
For other frameworks:
  • Add an inline script in the <head> to set the theme class before rendering
  • This script should run synchronously before the page renders

Build docs developers (and LLMs) love