Theme Toggle
InteractiveAn animated sun ⇄ moon theme button: a masking circle slides in to bite the sun into a crescent while rays retract and stars twinkle in — CSS-only, controlled or uncontrolled, SSR-safe.
Inspired by Skiper UI — theme toggles (free tier) — independently built, not affiliated. Attribution policy
Installation
npx shadcn@latest add https://parable-three.vercel.app/r/theme-toggle.jsonRuns the shadcn CLI — the component source is copied intocomponents/parable/theme-toggle.tsx. You own it from there.
Usage
The exact code rendering the preview above.
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 28, padding: 48, borderRadius: 28, background: "radial-gradient(130% 130% at 28% 18%, #1b1b30 0%, #0f0f10 68%)" }}>
<ThemeToggle defaultTheme="dark" syncDocumentClass={false} size={30} style={{ background: "rgba(255,255,255,0.045)" }} />
<ThemeToggle defaultTheme="light" syncDocumentClass={false} size={30} style={{ background: "rgba(255,255,255,0.045)" }} />
<ThemeToggle defaultTheme="dark" syncDocumentClass={false} size={46} sunColor="#f5a623" moonColor="#22d3ee" ringColor="#8b5cf6" style={{ background: "rgba(255,255,255,0.045)" }} />
</div>Props
ThemeToggleProps extends Omit< React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange" | "type" >
| Prop | Type | Default | Description |
|---|---|---|---|
| theme | Theme | — | Controlled theme. Pair with `onThemeChange`; the parent owns the DOM. |
| defaultTheme | Theme | "light" | Uncontrolled initial theme. Defaults to `"light"`. |
| onThemeChange | (theme: Theme) => void | — | Fires with the next theme whenever the button is toggled. |
| size | number | 20 | Icon edge length in px. Defaults to 20. |
| sunColor | string | "#f5a623" | Sun disc + ray colour. Mirrors the site `--pb-ember` token (#f5a623). |
| moonColor | string | "#22d3ee" | Moon disc + star colour. Mirrors the site `--pb-signal` token (#22d3ee). |
| ringColor | string | "#8b5cf6" | Focus-ring + hover colour. Mirrors the site `--pb-violet` token (#8b5cf6). |
| syncDocumentClass | boolean | true | When uncontrolled, toggle the `dark` class on `document.documentElement` and, on mount, sync the initial state from it. Set `false` for a purely visual toggle with no global side effects. Defaults to `true`. |