ui
Theme Toggle
A button component that allows users to toggle between light and dark themes.
Installation
1. Install next-themes
npm install next-themes2. Create ThemeProvider
Create a provider component at components/providers/theme-provider.tsx:
'use client'
import * as React from 'react'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { type ThemeProviderProps } from 'next-themes'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}3. Add ThemeProvider to root layout
Update your app/layout.tsx:
import { ThemeProvider } from '@/components/providers/theme-provider'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}4. Create ThemeToggle component
Create the toggle component at components/ui/theme-toggle.tsx:
'use client'
import * as React from 'react'
import { Moon, Sun } from 'lucide-react'
import { useTheme } from 'next-themes'
import { Button } from '@/design-system/components/ui/button'
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
<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>
)
}5. Add CSS variables for dark mode
Update your app/globals.css with dark mode variables:
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}Usage
Import and use
import { ThemeToggle } from '@/design-system/components/ui/theme-toggle'
export default function Header() {
return (
<header>
<div className="container mx-auto flex items-center justify-between">
<h1>My App</h1>
<ThemeToggle />
</div>
</header>
)
}In navigation
Add the theme toggle to your navigation or header component:
import { ThemeToggle } from '@/design-system/components/ui/theme-toggle'
import { Button } from '@/design-system/components/ui/button'
export function Navigation() {
return (
<nav className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<Button variant="ghost">Home</Button>
<Button variant="ghost">About</Button>
<Button variant="ghost">Contact</Button>
</div>
<ThemeToggle />
</nav>
)
}ThemeProvider Props
| Prop | Type | Default | Description |
|---|---|---|---|
| attribute | string | "class" | HTML attribute to apply theme to |
| defaultTheme | string | "system" | Default theme to use |
| enableSystem | boolean | true | Enable system theme detection |
| disableTransitionOnChange | boolean | false | Disable transitions when changing theme |
useTheme Hook
The useTheme hook from next-themes provides access to theme state and controls:
'use client'
import { useTheme } from 'next-themes'
export function ThemeInfo() {
const { theme, setTheme, systemTheme, resolvedTheme } = useTheme()
return (
<div>
<p>Current theme: {theme}</p>
<p>System theme: {systemTheme}</p>
<p>Resolved theme: {resolvedTheme}</p>
<button onClick={() => setTheme('light')}>Light</button>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('system')}>System</button>
</div>
)
}| Property | Type | Description |
|---|---|---|
| theme | string | Current theme value |
| setTheme | function | Function to set theme |
| systemTheme | string | System's preferred theme |
| resolvedTheme | string | Actual theme being applied |