Theme Toggle

Switches the page between light and dark themes.

Click to repaint the page from the cursor

Installation

pnpm dlx shadcn@latest add @paneui/theme-toggle

Usage

Examples

Light, Dark and System

useTheme

Use the hook for a menu or segmented control of your own.

Sizes

Reveal Duration

useThemeToggle

Motion

The reveal uses the View Transition API and starts at the click point, or at the button's centre for keyboard presses. With reduced motion, or without the API, the theme swaps instantly and the icon only cross-fades.

Persistence

The choice is saved to localStorage under storageKey ("light" or "dark"); choosing System removes the key, and no key means the page follows the OS, including OS changes while it is open. A theme-change window event (THEME_CHANGE_EVENT) fires, so several toggles stay in sync, and other tabs follow through the storage event.

Labels

The button is named by the locale: switchToLightTheme or switchToDarkTheme, themeCycle(current, next) with themeLight, themeDark and themeSystem in the tri-state mode, and toggleTheme before the theme is known. An aria-label prop wins.

Pre-paint script

Inline getThemeScript() in a <script> at the top of <head> so the first paint has the right theme: <script dangerouslySetInnerHTML={{ __html: getThemeScript() }} /> (pass your storageKey if you changed it). It sets the .dark class and color-scheme, and with no stored value keeps following OS changes even before the page hydrates.

API Reference

ThemeToggle

Round 32px button that flips the .dark class on <html>. Also accepts every prop of button.

iconSize= 16
number
Sun / moon icon size in px.
variant= "raised"
"raised" | "ghost"
Round raised button, or a 28px ghost toolbar button that matches size="icon-sm" ghost Buttons.
storageKey= "theme"
string
localStorage key for the saved theme.
duration= 560
number
Reveal duration in ms.
onThemeChange
(theme: "light" | "dark") => void
Called after the theme changes.
modes= "binary"
"binary" | "tri"
binary flips light and dark. tri cycles light, dark and system and shows the stored choice (a monitor icon for system).
PropTypeDefaultDescription
iconSizenumber16Sun / moon icon size in px.
variant"raised" | "ghost""raised"Round raised button, or a 28px ghost toolbar button that matches size="icon-sm" ghost Buttons.
storageKeystring"theme"localStorage key for the saved theme.
durationnumber560Reveal duration in ms.
onThemeChange(theme: "light" | "dark") => void—Called after the theme changes.
modes"binary" | "tri""binary"binary flips light and dark. tri cycles light, dark and system and shows the stored choice (a monitor icon for system).
data-mode
The current mode when modes="tri".
Data attributeDescription
data-modeThe current mode when modes="tri".

useTheme

Returns { theme, resolvedTheme, systemTheme, setTheme }. theme is the stored choice ("system" when nothing is stored), resolvedTheme the theme on the page. All are null until mounted.

storageKey= "theme"
string
localStorage key.
duration= 560
number
Reveal duration in ms.
onThemeChange
(theme: "light" | "dark") => void
Called after the theme on the page changes.
setTheme
(theme: "light" | "dark" | "system", options?: { origin?: { x: number; y: number } }) => void
Returned. Store a choice and reveal the result from origin; "system" removes the key.
PropTypeDefaultDescription
storageKeystring"theme"localStorage key.
durationnumber560Reveal duration in ms.
onThemeChange(theme: "light" | "dark") => void—Called after the theme on the page changes.
setTheme(theme: "light" | "dark" | "system", options?: { origin?: { x: number; y: number } }) => void—Returned. Store a choice and reveal the result from origin; "system" removes the key.

getThemeScript

(storageKey?: string) => string. Source of the pre-paint script.

useThemeToggle

Returns { theme, setTheme, toggle }. theme is null until mounted, then follows the .dark class.

storageKey= "theme"
string
localStorage key.
duration= 560
number
Reveal duration in ms.
onThemeChange
(theme: "light" | "dark") => void
Called after the theme changes.
setTheme
(theme: "light" | "dark", options?: { origin?: { x: number; y: number } }) => void
Returned. Switch to a theme, revealing from origin (defaults to the viewport centre).
toggle
(options?: { origin?: { x: number; y: number } }) => void
Returned. Switch to the other theme.
PropTypeDefaultDescription
storageKeystring"theme"localStorage key.
durationnumber560Reveal duration in ms.
onThemeChange(theme: "light" | "dark") => void—Called after the theme changes.
setTheme(theme: "light" | "dark", options?: { origin?: { x: number; y: number } }) => void—Returned. Switch to a theme, revealing from origin (defaults to the viewport centre).
toggle(options?: { origin?: { x: number; y: number } }) => void—Returned. Switch to the other theme.