Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Buttons

Pro

Theme Toggle Button for React

An icon button that switches between light and dark, turning the sun into the moon. For React with Base UI, when your header needs a theme switch that works with any theme setup.

Framework
UI library

Each press moves to the next theme, and the icon turns to match: a sun for light, a moon for dark and, with system, a monitor for the device's own setting. The button only reports the new theme through onThemeChange; your app applies it to the page.

Use it in a header or a settings bar, wherever people expect a one-press switch. When people need to see all the choices at once, a menu or a segmented control says more. It wraps the Button, so variant and size work as usual. It doesn't depend on any theme library: control it with theme and onThemeChange, or leave it uncontrolled. The theme is one data attribute on the icon slot, and CSS transitions turn, grow and fade the icons, with no animation library; with reduced motion they only cross-fade.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { ThemeToggleButton } from "@/components/ui/theme-toggle-button";
<ThemeToggleButton onThemeChange={setTheme} theme={theme} />

Demos

Controlled

Pass theme and update it from onThemeChange to keep the button in step with the rest of your app. Here a row of options and the toggle share one state, so changing either updates both. In your app, the same callback applies the theme to the page.

Preview width

System Theme

system adds a third step to the cycle: light, dark, then system, shown as a monitor, which means follow the device. Use it when your theme setup stores system as its own choice. The last one here is round and larger, with pill and size="icon-lg".

Preview width

Accessibility

The button is named by what it does next, such as Switch to dark theme, and the name changes after each press. Pass aria-label to use your own wording. The icons are hidden from assistive tech.

Enter
Switch to the next theme.
Space
Switch to the next theme.

FAQ

Why doesn't it change my page's theme?

It only reports the next theme, so it works with any setup: a theme provider, a class on the root element, or a stored setting. Apply the theme in onThemeChange, and pass the current one back as theme.

How do I use it with a theme provider?

Pass the provider's current theme as theme and its setter as onThemeChange. If the provider uses a system theme, add system so the button can show and choose it.

Do I have to pass theme?

No. Without it the button keeps its own state, starting from defaultTheme, which is light. onThemeChange still reports each change.

How do I avoid the wrong icon on first load?

The server doesn't know the visitor's theme, so render the button with the theme you'll show first, or wait until your provider has read the stored theme before passing theme.

Can it be bigger, or round?

Yes. It's an icon button sized icon-md by default; pass another icon size such as icon-lg, and pill to make it round.

What happens with reduced motion?

The icons cross-fade without turning or scaling. The theme and the button's name change the same way.

API Reference

ThemeToggleButton

data-slot="theme-toggle-button-icon"

PropType
theme?ThemeToggleTheme

The theme it shows, when you hold it yourself. Pair it with onThemeChange.

defaultTheme?ThemeToggleTheme

The theme it starts on when you don't pass theme.

Default
"light"
onThemeChange?(theme: ThemeToggleTheme) → void

Called with the next theme on every press. This is where you apply it to your page: the button never changes the page's theme itself.

Parameters

theme – ThemeToggleTheme

Returns
void
focusableWhenDisabled?boolean

Whether the button should be focusable when disabled.

Default
false
nativeButton?boolean

Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (for example, <div>).

Default
true
size?ButtonProps["size"]

The Button size; an icon size, since it shows only an icon.

Default
"icon-md"
system?boolean

Add the system theme to the cycle: light, dark, system, then light again. Without it, the button switches between light and dark.

Default
false
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-state?string

Mirrors shown.

  • Icon Button

    Hover reveals

    Icon toggles for other settings

  • Pro

    Flip Button

    Hover reveals

    Another two-state swap animation

  • Kbd Button

    Controls

    Give the toggle a keyboard shortcut

  • Pro

    Glass Button

    Surfaces

    A header button that suits both themes

  • Copy Button

    Progress & results

    Icon swap that confirms an action

  • Pro

    Liquid Button

    Hover reveals

    A fill effect for the active state

All 35 buttons