Why Not Just Toggle a Class?
The standard dark mode toggle flips a dark class on and calls it done. That works, but the instant color swap always felt a little abrupt to me — like flicking a light switch instead of anything intentional. I wanted the toggle to feel like a deliberate moment, not a flicker.
Three Phases, One Overlay
The theme toggle animates a single fixed, full-screen div in three phases:
wipe.style.transform = "scaleY(0)";
wipe.style.transformOrigin = "top";
wipe.style.transition = "transform 0.6s cubic-bezier(0.65, 0, 0.35, 1)";
wipe.style.transform = "scaleY(1)"; // cover
setTimeout(() => {
setTheme(next);
document.documentElement.classList.toggle("dark", next === "dark");
}, 620); // switch, while fully coveredBecause the actual class swap happens while the overlay is 100% opaque, there's no visible flash of mismatched colors mid-transition — the user only ever sees one theme or a solid wipe, never both at once.
Why This Skips React State for the Animation
The wipe itself is driven with direct style mutations on a ref, not useState. Animating 60fps transform/opacity changes through React re-renders is unnecessary work for something this transient — the DOM node is grabbed once via useRef, and the animation just mutates its style directly. React only gets involved for the one meaningful state change: which theme is active.
Avoiding the Flash on Load
Theme preference is read from localStorage in a useEffect, and the provider renders nothing extra until that read completes (mounted state). Combined with suppressHydrationWarning on , this avoids the classic dark-mode flash where the page briefly renders in the wrong theme before JavaScript catches up.
Result
Theme switching feels like an actual transition instead of a snap — and it's built entirely with refs and native style mutations, no animation library involved.