shadcn.io is an independent project built on shadcn/ui
Buttons
ProTheme 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.
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.
Installation
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.
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".
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.
API Reference
ThemeToggleButton
data-slot="theme-toggle-button-icon"
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.
data-state?string
Mirrors shown.




