> ## 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.

# ThemeSwitcher

> A dropdown component for selecting color themes and toggling light/dark mode

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:

```bash theme={null}
npx shadcn@latest add https://tweakcn-picker.vercel.app/r/nextjs/theme-system.json
```

## Usage

Add the `ThemeSwitcher` to your navigation or header:

```tsx title="components/layout/header.tsx" theme={null}
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

```tsx title="components/theme-switcher.tsx" {40-163} theme={null}
"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:

```tsx theme={null}
// 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:

```tsx theme={null}
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:

```tsx theme={null}
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:

```tsx theme={null}
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.

### Change Button Style

Customize the trigger button appearance:

```tsx theme={null}
<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>
```

### Adjust Menu Width

Change the dropdown menu width:

```tsx theme={null}
<DropdownMenuContent align="end" className="w-64"> {/* Changed from w-48 */}
```

### Modify Scroll Height

Adjust the scrollable area height:

```tsx theme={null}
<ScrollArea className="h-96"> {/* Changed from h-80 */}
```

### Filter Themes

Show only specific theme categories:

```tsx theme={null}
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`.

## Related

<CardGroup cols={2}>
  <Card title="useTheme Hook" icon="code" href="/usage/use-theme-hook">
    Control themes programmatically
  </Card>

  <Card title="Custom Selector" icon="wand-magic-sparkles" href="/usage/custom-theme-selector">
    Build your own theme selector
  </Card>
</CardGroup>
