Documentation Index Fetch the complete documentation index at: https://mintlify.com/BankkRoll/tweakcn-theme-picker/llms.txt
Use this file to discover all available pages before exploring further.
The ThemeSwitcher is a pre-built dropdown component that provides an intuitive UI for users to switch between color themes and toggle between light and dark modes.
Installation
The ThemeSwitcher is automatically included when you install the theme system:
npx shadcn@latest add https://tweakcn-picker.vercel.app/r/nextjs/theme-system.json
Usage
Add the ThemeSwitcher to your navigation or header:
components/layout/header.tsx
import { ThemeSwitcher } from "@/components/theme-switcher" ;
export function Header () {
return (
< header className = "border-b" >
< div className = "container flex items-center justify-between h-16" >
< nav className = "flex items-center gap-6" >
< a href = "/" > Home </ a >
< a href = "/about" > About </ a >
</ nav >
< ThemeSwitcher />
</ div >
</ header >
);
}
Features
The ThemeSwitcher provides:
Mode Toggle : Switch between light and dark modes with visual indicators
Color Theme Selection : Browse and select from all installed themes
Visual Previews : See color swatches for each theme
Current Selection : Check marks show the active theme and mode
Scrollable List : Handles large theme collections with a scrollable menu
Theme Attribution : Credits tweakcn in the footer
Implementation
components/theme-switcher.tsx
"use client" ;
import { Check , Moon , Palette , Sun } from "lucide-react" ;
import {
DropdownMenu ,
DropdownMenuContent ,
DropdownMenuItem ,
DropdownMenuLabel ,
DropdownMenuPortal ,
DropdownMenuSeparator ,
DropdownMenuSub ,
DropdownMenuSubContent ,
DropdownMenuSubTrigger ,
DropdownMenuTrigger ,
} from "@/components/ui/dropdown-menu" ;
import { sortedThemes , themes } from "@/lib/themes-config" ;
import { useEffect , useState } from "react" ;
import { Button } from "@/components/ui/button" ;
import { ScrollArea } from "@/components/ui/scroll-area" ;
import { cn } from "@/lib/utils" ;
import { useTheme } from "next-themes" ;
// Helper to parse theme string like "catppuccin-dark"
function parseTheme ( theme : string | undefined ) : {
colorTheme : string ;
mode : "light" | "dark" ;
} {
if ( ! theme ) return { colorTheme: "default" , mode: "dark" };
if ( theme . endsWith ( "-dark" )) {
return { colorTheme: theme . replace ( "-dark" , "" ), mode: "dark" };
}
if ( theme . endsWith ( "-light" )) {
return { colorTheme: theme . replace ( "-light" , "" ), mode: "light" };
}
return { colorTheme: "default" , mode: "dark" };
}
export function ThemeSwitcher () {
const [ mounted , setMounted ] = useState ( false );
const { theme , setTheme } = useTheme ();
useEffect (() => {
setMounted ( true );
}, []);
const { colorTheme , mode } = parseTheme ( theme );
const setColorTheme = ( name : string ) => {
setTheme ( ` ${ name } - ${ mode } ` );
};
const setMode = ( newMode : "light" | "dark" ) => {
setTheme ( ` ${ colorTheme } - ${ newMode } ` );
};
if ( ! mounted ) {
return (
< Button variant = "ghost" size = "icon" className = "h-9 w-9" >
< Sun className = "h-4 w-4" />
</ Button >
);
}
const currentThemeConfig =
themes . find (( t ) => t . name === colorTheme ) || themes [ 0 ];
const isDark = mode === "dark" ;
return (
< DropdownMenu >
< DropdownMenuTrigger asChild >
< Button variant = "ghost" size = "icon" className = "h-9 w-9" >
{ isDark ? < Moon className = "h-4 w-4" /> : < Sun className = "h-4 w-4" /> }
< span className = "sr-only" > Toggle theme </ span >
</ Button >
</ DropdownMenuTrigger >
< DropdownMenuContent align = "end" className = "w-48" >
< DropdownMenuLabel className = "flex items-center gap-2" >
< div
className = "h-3 w-3 rounded-full"
style = { {
backgroundColor: isDark
? currentThemeConfig . primaryDark
: currentThemeConfig . primaryLight ,
} }
/>
{ currentThemeConfig . title }
</ DropdownMenuLabel >
< DropdownMenuSeparator />
{ /* Mode Toggle */ }
< DropdownMenuItem
onClick = { () => setMode ( "light" ) }
className = { cn ( "flex items-center gap-2 cursor-pointer" ) }
>
< Sun className = "h-4 w-4" />
< span className = "flex-1" > Light </ span >
{ mode === "light" && < Check className = "h-4 w-4 text-primary" /> }
</ DropdownMenuItem >
< DropdownMenuItem
onClick = { () => setMode ( "dark" ) }
className = { cn ( "flex items-center gap-2 cursor-pointer" ) }
>
< Moon className = "h-4 w-4" />
< span className = "flex-1" > Dark </ span >
{ mode === "dark" && < Check className = "h-4 w-4 text-primary" /> }
</ DropdownMenuItem >
< DropdownMenuSeparator />
{ /* Color Theme Submenu */ }
< DropdownMenuSub >
< DropdownMenuSubTrigger className = "flex items-center gap-2" >
< Palette className = "h-4 w-4" />
< span > Color Theme </ span >
</ DropdownMenuSubTrigger >
< DropdownMenuPortal >
< DropdownMenuSubContent className = "w-52" >
< ScrollArea className = "h-80" >
< div className = "p-1" >
{ sortedThemes . map (( t ) => (
< DropdownMenuItem
key = { t . name }
onClick = { () => setColorTheme ( t . name ) }
className = "flex items-center gap-3 cursor-pointer"
>
< div
className = "h-4 w-4 rounded-full border border-border shrink-0"
style = { {
backgroundColor: isDark
? t . primaryDark
: t . primaryLight ,
} }
/>
< span className = "flex-1" > { t . title } </ span >
{ colorTheme === t . name && (
< Check className = "h-4 w-4 text-primary" />
) }
</ DropdownMenuItem >
)) }
</ div >
</ ScrollArea >
< DropdownMenuSeparator />
< div className = "px-2 py-1.5 text-xs text-muted-foreground text-center" >
Themes by { " " }
< a
href = "https://tweakcn.com"
target = "_blank"
rel = "noopener noreferrer"
className = "font-medium text-foreground hover:underline"
onClick = { ( e ) => e . stopPropagation () }
>
tweakcn
</ a >
</ div >
</ DropdownMenuSubContent >
</ DropdownMenuPortal >
</ DropdownMenuSub >
</ DropdownMenuContent >
</ DropdownMenu >
);
}
How It Works
Theme Parsing
The component uses a parseTheme helper function to extract the color theme name and mode from the combined theme string:
// Input: "catppuccin-dark"
// Output: { colorTheme: "catppuccin", mode: "dark" }
function parseTheme ( theme : string | undefined ) : {
colorTheme : string ;
mode : "light" | "dark" ;
} {
if ( ! theme ) return { colorTheme: "default" , mode: "dark" };
if ( theme . endsWith ( "-dark" )) {
return { colorTheme: theme . replace ( "-dark" , "" ), mode: "dark" };
}
if ( theme . endsWith ( "-light" )) {
return { colorTheme: theme . replace ( "-light" , "" ), mode: "light" };
}
return { colorTheme: "default" , mode: "dark" };
}
Mode Switching
When toggling between light and dark modes, the component preserves the current color theme:
const setMode = ( newMode : "light" | "dark" ) => {
// If currently on "catppuccin-dark" and switching to light:
// Result: "catppuccin-light"
setTheme ( ` ${ colorTheme } - ${ newMode } ` );
};
Color Theme Switching
When changing color themes, the component preserves the current mode:
const setColorTheme = ( name : string ) => {
// If currently on "catppuccin-dark" and switching to "vercel":
// Result: "vercel-dark"
setTheme ( ` ${ name } - ${ mode } ` );
};
Hydration Safety
The component uses a mounted state to prevent hydration mismatches:
const [ mounted , setMounted ] = useState ( false );
useEffect (() => {
setMounted ( true );
}, []);
if ( ! mounted ) {
return (
< Button variant = "ghost" size = "icon" className = "h-9 w-9" >
< Sun className = "h-4 w-4" />
</ Button >
);
}
Customization
Remove Attribution
If you want to remove the tweakcn attribution, delete lines 144-156 from the component.
Customize the trigger button appearance:
< DropdownMenuTrigger asChild >
< Button variant = "outline" size = "sm" className = "gap-2" >
{ isDark ? < Moon className = "h-4 w-4" /> : < Sun className = "h-4 w-4" /> }
< span > Theme </ span >
</ Button >
</ DropdownMenuTrigger >
Change the dropdown menu width:
< DropdownMenuContent align = "end" className = "w-64" > { /* Changed from w-48 */ }
Adjust the scrollable area height:
< ScrollArea className = "h-96" > { /* Changed from h-80 */ }
Filter Themes
Show only specific theme categories:
import { themes } from "@/lib/themes-config" ;
// Only show minimal themes
const minimalThemes = themes . filter (( t ) =>
[ "default" , "mono" , "mocha-mousse" , "modern-minimal" ]. includes ( t . name )
);
// Use minimalThemes instead of sortedThemes in the map
{ minimalThemes . map (( t ) => (
// ...
))}
Dependencies
The ThemeSwitcher requires these shadcn/ui components:
button
dropdown-menu
scroll-area
These are automatically installed with the theme system. Icons are provided by lucide-react.
useTheme Hook Control themes programmatically
Custom Selector Build your own theme selector