Theme Button

Provides an elegant and user-friendly way to cycle through light, dark, and system themes. It's specifically designed to work with next-themes, the most popular theme management library for Next.js applications.

npx shadcn@latest add @shuip/theme-button
pnpm dlx shadcn@latest add @shuip/theme-button
bun x shadcn@latest add @shuip/theme-button
'use client';

import { Laptop, Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import * as React from 'react';
import { Button } from '@/components/ui/button';

type Theme = 'system' | 'light' | 'dark';

export interface ThemeButtonProps extends React.ComponentProps<typeof Button> {
  withText?: boolean;
}

export function ThemeButton({ withText, ...props }: ThemeButtonProps) {
  const { theme, setTheme } = useTheme();
  const [currentTheme, setCurrentTheme] = React.useState<Theme>('system');

  React.useEffect(() => {
    setCurrentTheme(theme as Theme);
  }, [theme]);

  const cycleTheme = () => {
    const themes: Theme[] = ['system', 'light', 'dark'];
    const currentIndex = themes.indexOf(currentTheme);
    const nextIndex = (currentIndex + 1) % themes.length;
    setTheme(themes[nextIndex]);
  };

  const getThemeIcon = () => {
    switch (currentTheme) {
      case 'system':
        return <Laptop className='size-[1.2rem]' />;
      case 'light':
        return <Sun className='size-[1.2rem]' />;
      case 'dark':
        return <Moon className='size-[1.2rem]' />;
    }
  };

  const getThemeText = () => {
    return currentTheme.charAt(0).toUpperCase() + currentTheme.slice(1);
  };

  return (
    <Button variant='outline' size={withText ? 'default' : 'icon'} onClick={cycleTheme} {...props}>
      {getThemeIcon()}
      {withText && <span className='ml-2 capitalize'>{getThemeText()}</span>}
      <span className='sr-only'>Toggle theme</span>
    </Button>
  );
}

The command is the same for Base UI and Radix: the CLI reads the style in your components.json and installs the matching variant.

Loading...

Ready-to-use theme toggle

The button automatically cycles through these states:

  • System: Uses the user's OS preference (🖥️ Laptop icon)
  • Light: Forces light mode (☀️ Sun icon)
  • Dark: Forces dark mode (🌙 Moon icon)

With ThemeButton, a complete theme switcher in one line:

With ThemeButton - simple and complete

<ThemeButton />

With text label

<ThemeButton withText={true} />

Setup with next-themes

First, make sure you have next-themes installed and configured:

Examples

Default

Loading...
import { ThemeButton } from '@/components/ui/shuip/theme-button';

export default function ThemeButtonExample() {
  return <ThemeButton />;
}

Text

Loading...
import { ThemeButton } from '@/components/ui/shuip/theme-button';

export default function ThemeButtonTextExample() {
  return <ThemeButton withText />;
}

Props

Prop

Type

On this page