Skip to content

shadcn.io is an independent project built on shadcn/ui

Buttons

Pro

Theme Toggle Button for Solid

An icon button that switches light and dark, turning the sun into the moon. For Solid with Ark UI, when a header needs a theme switch for any setup.

UI library: Ark UI

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
clickEffect?boolean

Apply a click effect to the button.

Default
true
isLoading?boolean

Show a loading indicator.

Default
false
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
asChild?(props: ParentProps) => JSX.Element

Render your own element: a function that gets the part's props to spread on it.

Type
(props: ParentProps) => JSX.Element
class?string

Merged with the part's classes.

AttributeType
data-state?string

Mirrors shown.