From a973e0382e5619610e450387c4f8e446fb819728 Mon Sep 17 00:00:00 2001 From: Jagadeesh Date: Mon, 29 Jun 2026 01:37:37 +0530 Subject: [PATCH] feat: smoother theme transition timing for dark mode switch (#291) --- src/ThemeContext.tsx | 11 +++++++++++ src/styles/theme-transition.css | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/styles/theme-transition.css diff --git a/src/ThemeContext.tsx b/src/ThemeContext.tsx index 5dfec5c..19c87a1 100644 --- a/src/ThemeContext.tsx +++ b/src/ThemeContext.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; +import './styles/theme-transition.css'; type Theme = 'light' | 'dark' | 'system'; @@ -22,6 +23,16 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) { return (resolvedTheme === 'light' || resolvedTheme === 'dark') ? resolvedTheme : 'dark'; }); + // Enable color-token transitions only after the first paint, so the initial + // theme is applied instantly (no flash) while subsequent switches animate. + useEffect(() => { + const root = window.document.documentElement; + const id = window.requestAnimationFrame(() => { + root.classList.add('theme-transitions-ready'); + }); + return () => window.cancelAnimationFrame(id); + }, []); + useEffect(() => { localStorage.setItem('callora-theme', theme); diff --git a/src/styles/theme-transition.css b/src/styles/theme-transition.css new file mode 100644 index 0000000..829aec4 --- /dev/null +++ b/src/styles/theme-transition.css @@ -0,0 +1,31 @@ +/** + * theme-transition.css + * + * Smooth the light <-> dark theme switch by animating the color tokens + * (background, text and border colors) instead of swapping them instantly. + * + * The transition is only active once the ThemeProvider has mounted and added + * the `theme-transitions-ready` class to . This prevents the colors from + * animating on the very first paint (which would otherwise look like a flash + * of the wrong theme fading in). + */ + +html.theme-transitions-ready, +html.theme-transitions-ready body, +html.theme-transitions-ready *:not(.no-theme-transition) { + transition: + background-color var(--transition-speed, 240ms) ease, + border-color var(--transition-speed, 240ms) ease, + color var(--transition-speed, 240ms) ease, + fill var(--transition-speed, 240ms) ease, + stroke var(--transition-speed, 240ms) ease; +} + +/* Respect users who prefer reduced motion: switch instantly. */ +@media (prefers-reduced-motion: reduce) { + html.theme-transitions-ready, + html.theme-transitions-ready body, + html.theme-transitions-ready * { + transition: none !important; + } +}