PParable
All components

Theme Toggle

Interactive

An 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.json

Runs 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" >

PropTypeDefaultDescription
themeThemeControlled theme. Pair with `onThemeChange`; the parent owns the DOM.
defaultThemeTheme"light"Uncontrolled initial theme. Defaults to `"light"`.
onThemeChange(theme: Theme) => voidFires with the next theme whenever the button is toggled.
sizenumber20Icon edge length in px. Defaults to 20.
sunColorstring"#f5a623"Sun disc + ray colour. Mirrors the site `--pb-ember` token (#f5a623).
moonColorstring"#22d3ee"Moon disc + star colour. Mirrors the site `--pb-signal` token (#22d3ee).
ringColorstring"#8b5cf6"Focus-ring + hover colour. Mirrors the site `--pb-violet` token (#8b5cf6).
syncDocumentClassbooleantrueWhen 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`.