mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
fix(ui): prevent flash of light mode on dark-mode page loads
Adds a synchronous inline script in the root layout that applies the `dark` class to <html> before React hydrates, and gates ThemeContext's DOM-write effect on a `hydrated` flag so the initial `isDarkMode=false` state doesn't briefly undo the class the script set. https://claude.ai/code/session_01SkckoL68328QgB5ifQwntT
This commit is contained in:
parent
cf059b94cc
commit
2d573cfc34
2 changed files with 24 additions and 3 deletions
|
|
@ -13,6 +13,20 @@ export const metadata: Metadata = {
|
|||
icons: { icon: "./favicon.ico" },
|
||||
};
|
||||
|
||||
// Sets the `dark` class on <html> synchronously before React hydrates, so
|
||||
// returning dark-mode users don't see a flash of light theme on page load.
|
||||
const darkModeInitScript = `
|
||||
(function () {
|
||||
try {
|
||||
var stored = window.localStorage.getItem('litellm-dark-mode');
|
||||
if (stored === 'true') {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.style.colorScheme = 'dark';
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
`;
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
|
|
@ -20,6 +34,9 @@ export default function RootLayout({
|
|||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: darkModeInitScript }} />
|
||||
</head>
|
||||
<body className={inter.className}>
|
||||
<ReactQueryProvider>
|
||||
<AntdGlobalProvider>{children}</AntdGlobalProvider>
|
||||
|
|
|
|||
|
|
@ -44,14 +44,18 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children, accessTo
|
|||
const [logoUrl, setLogoUrl] = useState<string | null>(null);
|
||||
const [faviconUrl, setFaviconUrl] = useState<string | null>(null);
|
||||
const [isDarkMode, setIsDarkModeState] = useState<boolean>(false);
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
|
||||
// Hydrate dark mode from localStorage after mount to avoid SSR mismatch.
|
||||
// Hydrate state from the value the inline init script already applied
|
||||
// (see `darkModeInitScript` in app/layout.tsx). Until this runs we leave
|
||||
// the DOM untouched so the SSR'd class set by the script survives.
|
||||
useEffect(() => {
|
||||
setIsDarkModeState(readInitialDarkMode());
|
||||
setHydrated(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === "undefined") return;
|
||||
if (!hydrated || typeof document === "undefined") return;
|
||||
const root = document.documentElement;
|
||||
if (isDarkMode) {
|
||||
root.classList.add("dark");
|
||||
|
|
@ -65,7 +69,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children, accessTo
|
|||
} catch {
|
||||
// ignore localStorage write errors
|
||||
}
|
||||
}, [isDarkMode]);
|
||||
}, [isDarkMode, hydrated]);
|
||||
|
||||
const setIsDarkMode = useCallback((value: boolean) => {
|
||||
setIsDarkModeState(value);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue