Theme Toggle

Switches the document between light and dark. Uncontrolled by default: it reads the class already on the element rather than flashing to its own idea of the default on mount.

src/components/ui/theme-toggle.tsx

About

From the component's own source, where it was written.

Switches the document between light and dark.

The class goes on document.documentElement, not on a wrapper. .dark defines the token values on the element it lands on and everything below inherits them, so a wrapper deeper in the tree cannot switch a subtree back — a descendant has no way to undo variables set above it.

Uncontrolled by default: it reads the class already on the element, so it agrees with whatever the page shipped with instead of flashing to its own idea of the default on mount. Pass dark/onDarkChange when the app owns the state.

The read happens in the initialiser, not in an effect. Reading in an effect loses the race against the effect that writes the class: that one ran first with the default false and cleared .dark before anything had looked at it, so merely rendering the component turned a dark page light.

Install

npx astralyx-ui add theme-toggle

Usage

import { ThemeToggle } from '@/components/ui/theme-toggle'

<ThemeToggle />
<ThemeToggle dark={dark} onDarkChange={setDark} />

Composer

Change the props and the source below follows.

Props
tsx
<ThemeToggle />

Examples

Toggle

Remembering the choice

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

ThemeToggle

Switches the document between light and dark.

Extends Omit<ButtonProps, 'children' | 'onChange'>.

PropTypeDefaultDescription
color'neutral' | 'blue' | 'violet' | 'cyan' | 'green' | 'amber' | 'rose' | 'destructive'——
variant'link' | 'default' | 'colored' | 'secondary' | 'outline' | 'ghost''secondary'Passed through to Button.
size'sm' | 'default' | 'xs' | 'lg' | 'xl' | 'icon-xs' | 'icon-sm' | 'icon' | 'icon-lg' | 'icon-xl''icon-sm'Passed through to Button.
asChildboolean—Render the child element instead of a <button>, keeping the styles.
tintstring—Any CSS colour, used instead of a named color set. Fill, text, tint and border are all derived from it, and every variant keeps working.
darkboolean—Controlled mode. Omit both and it manages itself.
onDarkChange(dark: boolean) => void—Controlled mode. Omit both and it manages itself.
targetdocument.documentElement—The class goes on the root, never a wrapper: .dark defines the token values on the element it lands on, so a descendant cannot switch a subtree back.

Behaviour

TopicDetailDescription
persistenceyours to addThe toggle does not remember the choice — it has no idea where your app keeps state. Store it, and re-apply the class from an inline script in <head> so the page does not paint the wrong palette first. Without both halves, every reload falls back to whatever the HTML shipped with.