Default to the dark theme for a fresh visitor

With no stored preference the app now resolves to dark instead of
tracking the OS. An explicit choice still wins: light/dark persist as
before, and "system" is now persisted verbatim rather than as a
cleared key, so an explicit OS-tracking choice stays distinguishable
from a never-chosen state and survives a reload. The first-paint
bootstrap script mirrors the same resolution so a fresh visitor sees
no flash to light.
This commit is contained in:
Marc-André Bombeck committed 2026-06-07 14:24:59 +02:00
1 parent 74a245a29c
commit f0b26141ed
2 files changed
+15 -7

No files matched your search

+1 -1
View File
@@ -90,7 +90,7 @@ export const viewport: Viewport = {
};
// Inline script to apply theme before first paint (prevents FOUC)
const themeScript = `(function(){try{var t=localStorage.getItem("healthlog-theme");var c=(t==="light"||t==="dark")?t:(window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light");document.documentElement.classList.add(c)}catch(e){document.documentElement.classList.add("dark")}})()`;
const themeScript = `(function(){try{var t=localStorage.getItem("healthlog-theme");var c=(t==="light"||t==="dark")?t:(t==="system"?(window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light"):"dark");document.documentElement.classList.add(c)}catch(e){document.documentElement.classList.add("dark")}})()`;
export default async function RootLayout({
children,
+14 -6
View File
@@ -49,16 +49,21 @@ function applyTheme(resolved: "dark" | "light") {
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setThemeState] = useState<Theme>(() => {
if (typeof window === "undefined") return "system";
if (typeof window === "undefined") return "dark";
const saved = localStorage.getItem("healthlog-theme") as Theme | null;
return saved === "light" || saved === "dark" ? saved : "system";
// A stored "light"/"dark"/"system" choice wins. With no stored
// preference the app defaults to dark rather than tracking the OS.
if (saved === "light" || saved === "dark") return saved;
if (saved === "system") return "system";
return "dark";
});
const [resolvedTheme, setResolvedTheme] = useState<"dark" | "light">(() => {
if (typeof window === "undefined") return "dark";
const saved = localStorage.getItem("healthlog-theme") as Theme | null;
const t = saved === "light" || saved === "dark" ? saved : "system";
return t === "system" ? getSystemTheme() : t;
if (saved === "light" || saved === "dark") return saved;
if (saved === "system") return getSystemTheme();
return "dark";
});
// Apply theme on mount (inline script handles FOUC, this ensures classes are in sync)
@@ -82,13 +87,16 @@ function ThemeProvider({ children }: { children: ReactNode }) {
const setTheme = useCallback((next: Theme) => {
setThemeState(next);
// Persist every explicit choice, including "system" — the absence of a
// stored value is reserved for a fresh visitor and now defaults to dark.
// Storing "system" verbatim keeps an explicit OS-tracking choice
// distinguishable from "never chose", so a reload honours it.
localStorage.setItem("healthlog-theme", next);
if (next === "system") {
localStorage.removeItem("healthlog-theme");
const resolved = getSystemTheme();
setResolvedTheme(resolved);
applyTheme(resolved);
} else {
localStorage.setItem("healthlog-theme", next);
setResolvedTheme(next);
applyTheme(next);
}