npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

shadcn-theme-switcher

v0.1.0

Published

A flexible theme switcher component for shadcn/ui with support for light/dark mode and multiple color themes

Readme

shadcn-theme-switcher

A flexible, production-ready theme switcher for shadcn/ui with support for multiple themes options with light/dark theme mode.

Usecases

Perfect for:

  • 🎯 User Personalization - Allow your users to personalize their experience with multiple theme options.
  • 🧪 Design Exploration - Quickly prototype and compare different themes before committing to a final one.
  • 🏢 Multi-Brand Apps - Support different brands or clients within a single application by customizing to their color schemes dynamically.
  • Accessibility - Provide theme variations optimized for different visual preferences and needs.

Features

  • 🎨 Multiple Color Themes - Switch between different color schemes seamlessly
  • 🌓 Light/Dark Mode - Support for light, dark, and system preference modes
  • 💾 Persistent Storage - Themes and modes persist across sessions using localStorage
  • 🔄 Cross-Tab Sync - Theme changes sync automatically across browser tabs
  • 🎭 Custom Themes - Easily create and use your own custom themes
  • 🔤 Google Fonts - Automatic font loading for themed typography
  • Zero Config - Works out of the box with sensible defaults
  • 🪝 Headless Hooks - Use the hooks directly for custom implementations
  • 📦 Tree-Shakeable - Only import what you need

Installation

npm install shadcn-theme-switcher

Peer Dependencies

This package requires the following peer dependencies (which should already be installed in your shadcn/ui project):

npm install class-variance-authority clsx tailwind-merge tailwindcss

Quick Start

Just add the Theme Switcher components and provide the themes.

import { ThemeSwitcher, ModeSwitcher } from "shadcn-theme-switcher";
import { defaultThemes } from "shadcn-theme-switcher/themes";

function App() {
  return (
    <nav>
      <ThemeSwitcher themes={defaultThemes} defaultTheme="default" />
      <ModeSwitcher defaultMode="system" />
    </nav>
  );
}

That's it! Your app now has theme switching capabilities.

Components

ModeSwitcher

A dropdown component for switching between light, dark, and system modes.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | defaultMode | "light" \| "dark" \| "system" | "system" | Initial mode when no preference is stored | | className | string | undefined | Additional CSS classes |

Example

import { ModeSwitcher } from "shadcn-theme-switcher";

function Navigation() {
  return <ModeSwitcher defaultMode="dark" className="w-32" />;
}

ThemeSwitcher

A dropdown component for switching between different color themes with theme palette previews.

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | themes | ThemeConfig[] | Yes | Array of theme configurations | | defaultTheme | string | No | Initial theme name (uses first theme if not provided) | | className | string | No | Additional CSS classes |

Example

import { ThemeSwitcher } from "shadcn-theme-switcher";
import { defaultThemes } from "shadcn-theme-switcher/themes";

function Navigation() {
  return (
    <ThemeSwitcher 
      themes={defaultThemes} 
      defaultTheme="violet-bloom"
      className="min-w-[200px]"
    />
  );
}

Hooks

For more control or custom implementations, use the hooks directly.

useThemeMode

Manages light/dark mode state and system preference detection.

Returns

| Property | Type | Description | |----------|------|-------------| | mode | "light" \| "dark" \| "system" | Current mode | | setMode | (mode: ThemeMode) => void | Function to change mode | | effectiveMode | "light" \| "dark" | Resolved mode (system → light/dark) |

Example

import { useThemeMode } from "shadcn-theme-switcher";

function CustomModeSwitcher() {
  const { mode, setMode, effectiveMode } = useThemeMode({ 
    defaultMode: "system" 
  });

  return (
    <div>
      <p>Current Mode: {mode}</p>
      <p>Effective Mode: {effectiveMode}</p>
      <button onClick={() => setMode("light")}>Light</button>
      <button onClick={() => setMode("dark")}>Dark</button>
      <button onClick={() => setMode("system")}>System</button>
    </div>
  );
}

useTheme

Manages color theme state and persistence.

Returns

| Property | Type | Description | |----------|------|-------------| | theme | string | Current theme name | | setTheme | (theme: string) => void | Function to change theme |

Note: You have to handle the font loading yourself, use applyThemeFonts

Example

import {useEffect} from "react"
import { useTheme, applyThemeFonts } from "shadcn-theme-switcher";
import { defaultThemes } from "shadcn-theme-switcher/themes";

function CustomThemeSwitcher() {
  const { theme, setTheme } = useTheme({ defaultTheme: "default" });

  const currentTheme = defaultThemes.find((t) => t.name === theme);

  useEffect(() => {
    const fonts = currentTheme?.fonts;
    if (fonts) applyThemeFonts(fonts);
  }, [currentTheme]);

  return (
    <div>
      <p>Current Theme: {theme}</p>
      <button onClick={() => setTheme("violet-bloom")}>Violet Bloom</button>
      <button onClick={() => setTheme("mocha-mousse")}>Mocha Mousse</button>
    </div>
  );
}

Default Themes

The package includes 15 pre-built themes:

| Theme Name | Description | |------------|-------------| | default | The standard shadcn/ui theme | | modern-minimal | Clean and modern minimalist design | | violet-bloom | Vibrant violet and purple palette | | t3-chat | Chat-inspired warm tones | | twitter | Classic Twitter blue theme | | mocha-mousse | Warm coffee-inspired browns | | bubblegum | Playful pink and pastel colors | | amethyst-haze | Mystical purple haze | | graphite | Sleek graphite gray tones | | cosmic-night | Deep cosmic purple palette | | mono | Pure monochrome design | | notebook | Paper-like notebook aesthetic | | doom-64 | Retro gaming-inspired colors | | catppuccin | Pastel soothing color scheme | | perpetuity | Terminal-style teal theme | | tangerine | Warm tangerine orange accents |

Import them with:

import { defaultThemes } from "shadcn-theme-switcher/themes";

Creating Custom Themes

Step 1: Define CSS Variables

Create a CSS file with your theme's color variables using OKLCH format:

/* custom-themes.css */

/* Common settings for both light and dark modes */
:root[data-theme="forest-green"],
[data-theme="forest-green"] {
  /* Optional: Custom fonts */
  --font-sans: Poppins, sans-serif;
  --font-mono: Fira Code, monospace;
  --font-serif: Georgia, serif;
  
  /* Border radius */
  --radius: 0.5rem;
  
  /* Optional: Letter spacing adjustments */
  --tracking-normal: 0em;
}

/* Light mode colors */
:root[data-theme="forest-green"]:not(.dark),
/* This extra selector applies to non root elements such as theme palette preview */
[data-theme="forest-green"]:not(.dark) {
  --background: oklch(1 0 0);
  --foreground: oklch(0.3 0.08 145);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.3 0.08 145);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.3 0.08 145);
  --primary: oklch(0.5 0.15 145);
  --primary-foreground: oklch(1 0 0);
  --secondary: oklch(0.95 0.02 145);
  --secondary-foreground: oklch(0.3 0.08 145);
  --muted: oklch(0.97 0.01 145);
  --muted-foreground: oklch(0.5 0.03 145);
  --accent: oklch(0.92 0.04 145);
  --accent-foreground: oklch(0.35 0.1 145);
  --destructive: oklch(0.6 0.2 25);
  --destructive-foreground: oklch(1 0 0);
  --border: oklch(0.92 0.02 145);
  --input: oklch(0.92 0.02 145);
  --ring: oklch(0.5 0.15 145);
}

/* Dark mode colors */
:root[data-theme="forest-green"].dark,
[data-theme="forest-green"].dark {
  --background: oklch(0.2 0.02 145);
  --foreground: oklch(0.95 0.01 145);
  --card: oklch(0.25 0.02 145);
  --card-foreground: oklch(0.95 0.01 145);
  --popover: oklch(0.25 0.02 145);
  --popover-foreground: oklch(0.95 0.01 145);
  --primary: oklch(0.6 0.15 145);
  --primary-foreground: oklch(1 0 0);
  --secondary: oklch(0.3 0.03 145);
  --secondary-foreground: oklch(0.95 0.01 145);
  --muted: oklch(0.3 0.03 145);
  --muted-foreground: oklch(0.7 0.02 145);
  --accent: oklch(0.4 0.1 145);
  --accent-foreground: oklch(0.85 0.05 145);
  --destructive: oklch(0.6 0.2 25);
  --destructive-foreground: oklch(1 0 0);
  --border: oklch(0.35 0.03 145);
  --input: oklch(0.35 0.03 145);
  --ring: oklch(0.6 0.15 145);
}

Step 2: Define Theme Configuration

import type { ThemeConfig } from "shadcn-theme-switcher";
import "./custom-themes.css"; 

export const myCustomThemes: ThemeConfig[] = [
  {
    name: "forest-green",
    title: "Forest Green",
    description: "Inspired by nature",
    fonts: [
      { name: "Poppins", weights: [400, 600, 700] },
      { name: "Fira Code" }
    ]
  }
];

Step 3: Import and Use

import { ThemeSwitcher } from "shadcn-theme-switcher";
import { myCustomThemes } from "./custom-themes";

// Not required if already imported in your custom-themes file
import "./custom-themes.css"; 

function App() {
  return <ThemeSwitcher themes={myCustomThemes} />;
}

API Reference

Utility Functions

applyMode(mode: ThemeMode): void

Manually apply a theme mode to the document.

import { applyMode } from "shadcn-theme-switcher";

applyMode("dark"); // Adds 'dark' class to document.documentElement

applyTheme(themeName: string): void

Manually apply a theme to the document.

import { applyTheme } from "shadcn-theme-switcher";

applyTheme("violet-bloom"); // Sets data-theme="violet-bloom" on document.documentElement

applyThemeFonts(fonts: ThemeFont[]): void

Manually load Google Fonts.

import { applyThemeFonts } from "shadcn-theme-switcher";

applyThemeFonts([
  { name: "Inter", weights: [400, 600] },
  { name: "Fira Code" }
]); // Smartly handles font link tags

getSystemTheme(): "light" | "dark"

Get the current system color scheme preference.

import { getSystemTheme } from "shadcn-theme-switcher";

const systemPreference = getSystemTheme();
console.log(systemPreference); // "light" or "dark"

getEffectiveMode(mode: ThemeMode): "light" | "dark"

Resolve "system" mode to actual light/dark value.

import { getEffectiveMode } from "shadcn-theme-switcher";

const effective = getEffectiveMode("system");
console.log(effective); // "light" or "dark" based on system preference

Advanced Usage

Headless Implementation

Create completely custom UI using the hooks:

import { useTheme, useThemeMode } from "shadcn-theme-switcher";
import { Sun, Moon, Laptop } from "lucide-react";
import "your-custom-themes.css"

function CustomThemeSwitcher() {
  const { theme, setTheme } = useTheme();
  const { mode, setMode, effectiveMode } = useThemeMode();

  const themes = [
    { id: "default", name: "Default" },
    { id: "violet-bloom", name: "Violet" }
  ];

  return (
    <div className="flex gap-4">
      {/* Mode Buttons */}
      <div className="flex gap-2">
        <button 
          onClick={() => setMode("light")}
          className={mode === "light" ? "active" : ""}
        >
          <Sun size={20} />
        </button>
        <button 
          onClick={() => setMode("dark")}
          className={mode === "dark" ? "active" : ""}
        >
          <Moon size={20} />
        </button>
        <button 
          onClick={() => setMode("system")}
          className={mode === "system" ? "active" : ""}
        >
          <Laptop size={20} />
        </button>
      </div>

      {/* Theme Buttons */}
      <div className="flex gap-2">
        {themes.map(t => (
          <button
            key={t.id}
            onClick={() => setTheme(t.id)}
            className={theme === t.id ? "active" : ""}
          >
            {t.name}
          </button>
        ))}
      </div>
    </div>
  );
}

SSR/SSG Support

For Next.js or other SSR frameworks, prevent hydration mismatches:

"use client"; // For Next.js App Router

import { useEffect, useState } from "react";
import { ModeSwitcher } from "shadcn-theme-switcher";

function Navigation() {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted) {
    return <div className="w-32 h-10" />; // Placeholder
  }

  return <ModeSwitcher />;
}

Cross-Tab Synchronization

Theme changes automatically sync across tabs. You can also listen to changes:

import { useEffect } from "react";
import { useTheme, useThemeMode } from "shadcn-theme-switcher";

function SyncedComponent() {
  const { theme } = useTheme();
  const { mode } = useThemeMode();

  useEffect(() => {
    console.log("Theme changed:", theme);
    // React to theme changes from other tabs
  }, [theme]);

  useEffect(() => {
    console.log("Mode changed:", mode);
    // React to mode changes from other tabs
  }, [mode]);

  return <div>Current: {theme} ({mode})</div>;
}

Programmatic Theme Loading

Load themes dynamically based on user preferences or API data:

import { useState, useEffect } from "react";
import { ThemeSwitcher } from "shadcn-theme-switcher";
import type { ThemeConfig } from "shadcn-theme-switcher";

function DynamicThemeSwitcher() {
  const [themes, setThemes] = useState<ThemeConfig[]>([]);

  useEffect(() => {
    // Fetch themes from API
    fetch("/api/themes")
      .then(res => res.json())
      .then(data => setThemes(data));
  }, []);

  useEffect(() => {
    // Load the theme css file dynamically
    const link = document.createElement("link");

    link.type = "text/css";
    link.rel = "stylesheet";
    // Your theme css file destination
    link.href = `/api/themes/${theme}/styles.css`;

    document.head.appendChild(link);
    return () => { document.head.removeChild(link); }
  }, [theme])

  if (themes.length === 0) {
    return <div>Loading themes...</div>;
  }

  return <ThemeSwitcher themes={themes} />;
}

Troubleshooting

CSS Not Loading

Problem: Theme colors aren't being applied.

Solutions:

  1. Ensure CSS is imported - In some rare cases you might have to import the themes.css file in your entry point:

    import "shadcn-theme-switcher/themes.css";
  2. Verify data-theme attribute - Check that data-theme is set on the root element:

    // Open DevTools and inspect <html> element
    // Should see: <html data-theme="violet-bloom">
  3. Check CSS specificity - Ensure theme styles aren't being overridden:

    /* Your theme CSS should target the data-theme attribute */
    [data-theme="my-theme"] {
      --primary: oklch(0.6231 0.188 259.8145);
    }

Theme Flash on Load (FOUC)

Problem: You see a brief flash of the wrong theme when the page loads.

Solutions:

  1. Add inline script - Prevent flash by setting theme before React hydrates:

    <!-- In your index.html -->
    <script>
      try {
        const theme = localStorage.getItem('app-theme') || 'default';
        const mode = localStorage.getItem('app-theme-mode') || 'system';
        document.documentElement.setAttribute('data-theme', theme);
           
        if (mode === 'dark' || (mode === 'system' && 
            window.matchMedia('(prefers-color-scheme: dark)').matches)) {
          document.documentElement.classList.add('dark');
        }
      } catch (e) {}
    </script>
  2. For Next.js, use the next-themes pattern:

    // app/layout.tsx
    export default function RootLayout({ children }) {
      return (
        <html suppressHydrationWarning>
          <head>
            <script dangerouslySetInnerHTML={{
              __html: `
                try {
                  const theme = localStorage.getItem('app-theme') || 'default';
                  const mode = localStorage.getItem('app-theme-mode') || 'system';
                  document.documentElement.setAttribute('data-theme', theme);
                  if (mode === 'dark' || (mode === 'system' && 
                      window.matchMedia('(prefers-color-scheme: dark)').matches)) {
                    document.documentElement.classList.add('dark');
                  }
                } catch (e) {}
              `
            }} />
          </head>
          <body>{children}</body>
        </html>
      );
    }

Fonts Not Loading

Problem: Google Fonts specified in theme config aren't loading.

Solutions:

  1. Check font names - Ensure font names match Google Fonts exactly:

    // ✅ Correct
    fonts: [{ name: "Plus Jakarta Sans" }]
       
    // ❌ Incorrect
    fonts: [{ name: "Plus-Jakarta-Sans" }]
  2. Apply fonts manually if needed:

    import { applyThemeFonts } from "shadcn-theme-switcher";
       
    useEffect(() => {
      applyThemeFonts([
        { name: "Inter", weights: [400, 600, 700] }
      ]);
    }, []);
  3. Use CSS font-family - Reference the loaded font in your CSS:

    [data-theme="my-theme"] {
      font-family: "Inter", sans-serif;
    }

Theme Not Persisting

Problem: Theme resets on page reload.

Solutions:

  1. Check localStorage access - Ensure localStorage is available:

    // Test in browser console
    localStorage.setItem('test', 'value');
    console.log(localStorage.getItem('test')); // Should log 'value'
  2. Verify storage keys - Check that the correct keys are being used:

    // Theme key: 'app-theme'
    // Mode key: 'app-theme-mode'
    console.log(localStorage.getItem('app-theme'));
    console.log(localStorage.getItem('app-theme-mode'));
  3. Private/Incognito mode - localStorage may be disabled in private browsing.

Styles Conflict with shadcn/ui

Problem: Theme switcher styles conflict with your shadcn/ui components.

Solutions:

  1. Namespace conflict - The package uses .shadcn-theme-switcher class:

    // All components are wrapped with this class
    <div className="shadcn-theme-switcher">...</div>
  2. Override styles - Use higher specificity if needed:

    .my-app .shadcn-theme-switcher {
      /* Your overrides */
    }
  3. Use custom implementation - Build your own UI with the hooks:

    import { useTheme } from "shadcn-theme-switcher";
    // Build custom component without using pre-built components

Cross-Tab Sync Not Working

Problem: Theme changes don't sync across browser tabs.

Solutions:

  1. Check if using same origin - Cross-tab sync only works on same domain/port.

  2. Test storage events - Verify events are firing:

    useEffect(() => {
      const handler = (e: StorageEvent) => {
        console.log("Storage changed:", e.key, e.newValue);
      };
      window.addEventListener("storage", handler);
      return () => window.removeEventListener("storage", handler);
    }, []);
  3. Same tab updates - Use custom events for updates in the same tab (already handled by the package).

Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • Modern mobile browsers

Requires support for:

  • CSS custom properties
  • prefers-color-scheme media query
  • localStorage API
  • CustomEvent API

Contributing

Contributions are welcome! Please check the GitHub repository for guidelines.

License

MIT © Nishant Mogha

Credits

Built with:


Need help? Open an issue or check existing discussions.