This component no longer exists. It was in ruixen-ui’s manifest when we first indexed it and is not there now — the registry served its inventory on 2026-08-12 and this item was not on it. The install command below will fail. It is kept here because a tutorial somewhere still tells people to run it.
Theme Toggler
ruixen-ui/animated-theme-togglerRemovedComponent
Sun↔moon morph toggle with spring physics, SVG mask crescent, and switch-click audio.
Live preview
live · rendered by the registry
Rendered by ruixen-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ruixen.com/r/animated-theme-toggler.jsonSource
1"use client";23import { useState, useEffect, useRef, useId } from "react";4import { motion } from "motion/react";56/**7 * Animated Theme Toggler — sun↔moon morph.8 *9 * Sun rays shrink and rotate away.10 * Center circle swells into a moon body.11 * A mask carves the crescent. Spring physics throughout.12 * A soft switch-click sounds on toggle.13 *14 * No shadcn. No lucide. Just motion/react.15 */1617/* ── Types ── */1819export interface AnimatedThemeTogglerProps {20 sound?: boolean;21}2223/* ── Audio ── */2425let _ctx: AudioContext | null = null;26let _buf: AudioBuffer | null = null;2728function audioCtx() {29 if (!_ctx) {30 _ctx = new (window.AudioContext ||31 (window as unknown as { webkitAudioContext: typeof AudioContext })32 .webkitAudioContext)();33 }34 if (_ctx.state === "suspended") _ctx.resume();35 return _ctx;36}3738function ensureBuf(ac: AudioContext): AudioBuffer {39 if (_buf && _buf.sampleRate === ac.sampleRate) return _buf;40 const rate = ac.sampleRate;41 const len = Math.floor(rate * 0.006);42 const buf = ac.createBuffer(1, len, rate);43 const ch = buf.getChannelData(0);44 for (let i = 0; i < len; i++) {45 const t = i / len;46 const sine = Math.sin(2 * Math.PI * 3400 * t);47 const noise = Math.random() * 2 - 1;48 ch[i] = (sine * 0.6 + noise * 0.4) * (1 - t) ** 3;49 }50 _buf = buf;51 return buf;52}5354function tick(last: React.MutableRefObject<number>) {55 const now = performance.now();56 if (now - last.current < 80) return;57 last.current = now;58 try {59 const ac = audioCtx();60 const buf = ensureBuf(ac);61 const src = ac.createBufferSource();62 const gain = ac.createGain();63 src.buffer = buf;64 gain.gain.value = 0.08;65 src.connect(gain);66 gain.connect(ac.destination);67 src.start();68 } catch {69 /* silent */70 }71}7273/* ── Component ── */7475export function AnimatedThemeToggler({76 sound = true,77}: AnimatedThemeTogglerProps) {78 const rawId = useId();79 const maskId = `att${rawId.replace(/:/g, "")}`;80 const lastSnd = useRef(0);81 const isFirst = useRef(true);82 const [isDark, setIsDark] = useState(false);8384 useEffect(() => {85 setIsDark(document.documentElement.classList.contains("dark"));86 requestAnimationFrame(() => {87 isFirst.current = false;88 });89 }, []);9091 const toggle = () => {92 const dark = document.documentElement.classList.toggle("dark");93 setIsDark(dark);94 if (sound) tick(lastSnd);95 };9697 const spring = isFirst.current98 ? { duration: 0 }99// … truncated
What it pulls in
npm packages
