Icon
Toggle
Segmented
Installation
This installs next-themes and lucide-react if they are not already in your project.
Setup
Wrap your app with ThemeProvider once (usually in the root layout). Use the class strategy so Motoko / Tailwind dark: variants work:
import { ThemeProvider } from "@/components/ui/theme-switcher"
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
)
}Make sure your CSS defines light tokens on :root and dark overrides under .dark (same pattern as shadcn).
Usage
import { ThemeSwitcher } from "@/components/ui/theme-switcher"
export default function Example() {
return <ThemeSwitcher />
}Variants
<ThemeSwitcher variant="icon" />
<ThemeSwitcher variant="toggle" />
<ThemeSwitcher variant="segmented" />With system preference
<ThemeSwitcher
variant="segmented"
themes={["light", "dark", "system"]}
showLabels
/>Controlled
"use client"
import * as React from "react"
import { ThemeSwitcher, type ThemeMode } from "@/components/ui/theme-switcher"
export default function ControlledExample() {
const [theme, setTheme] = React.useState<ThemeMode>("system")
return (
<ThemeSwitcher variant="segmented" value={theme} onThemeChange={setTheme} />
)
}Props
ThemeSwitcher
| Prop | Type | Default |
|---|---|---|
variant | "icon" | "toggle" | "segmented" | "icon" |
themes | ("light" | "dark" | "system")[] | ["light","dark"] / segmented: + "system" |
value | "light" | "dark" | "system" | — |
defaultValue | "light" | "dark" | "system" | — |
onThemeChange | (theme: ThemeMode) => void | — |
size | "sm" | "default" | "lg" | "default" |
disableAnimation | boolean | false |
showLabels | boolean | false |
labels | Partial<Record<ThemeMode, string>> | — |
className | string | — |
disabled | boolean | false |
ThemeProvider
Re-export of next-themes ThemeProvider. Common props:
| Prop | Type | Notes |
|---|---|---|
attribute | string | Use "class" for Motoko / Tailwind dark |
defaultTheme | string | e.g. "system" |
enableSystem | boolean | Follow OS preference when theme is system |
disableTransitionOnChange | boolean | Optional; skips CSS flash on theme change |
Notes
- Animations are CSS-only (
cubic-bezier(0.2, 0, 0, 1)) — nomotiondependency. - Icon swap uses opacity / scale / blur cross-fade so enter and exit stay interruptible.
- A short skeleton renders until mount to avoid hydration mismatch with
next-themes.