From 4ac92dac7235619c8811f471729fca4be2ba1f0a Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 5 Sep 2026 16:57:39 -0500 Subject: [PATCH] Sync PWA status bar theme-color with resolved app theme (#24203) * sync PWA status bar theme-color with resolved app theme * update tags on login page too * track system theme preference * revert --- web/index.html | 1 + web/login.html | 1 + web/src/context/theme-provider.tsx | 51 +++++++++++++++++++++++++----- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/web/index.html b/web/index.html index bb019d4263..37e5aefd1e 100644 --- a/web/index.html +++ b/web/index.html @@ -27,6 +27,7 @@ +
diff --git a/web/login.html b/web/login.html index f83243d023..c4674a0ae3 100644 --- a/web/login.html +++ b/web/login.html @@ -27,6 +27,7 @@ +
diff --git a/web/src/context/theme-provider.tsx b/web/src/context/theme-provider.tsx index 1112bf925d..98b8d05f9e 100644 --- a/web/src/context/theme-provider.tsx +++ b/web/src/context/theme-provider.tsx @@ -1,6 +1,7 @@ import { createContext, useContext, useEffect, useMemo, useState } from "react"; type Theme = "dark" | "light" | "system"; +type ActiveTheme = Exclude; type ColorScheme = | "theme-blue" | "theme-green" @@ -51,6 +52,32 @@ const initialState: ThemeProviderState = { const ThemeProviderContext = createContext(initialState); +function updateThemeMetaTags(theme: ActiveTheme): void { + const isDark = theme === "dark"; + const metaDefinitions = [ + { name: "theme-color", content: isDark ? "#000000" : "#ffffff" }, + { + name: "apple-mobile-web-app-status-bar-style", + content: isDark ? "black" : "default", + }, + ]; + + for (const { name, content } of metaDefinitions) { + const tags = document.head.querySelectorAll( + `meta[name="${name}"]`, + ); + const matchingTags = + tags.length > 0 + ? Array.from(tags) + : [document.head.appendChild(document.createElement("meta"))]; + + for (const tag of matchingTags) { + tag.name = name; + tag.content = content; + } + } +} + export function ThemeProvider({ children, defaultTheme = "system", @@ -82,15 +109,21 @@ export function ThemeProvider({ } }); - const systemTheme = useMemo(() => { - if (theme != "system") { - return undefined; - } + const [systemPrefersDark, setSystemPrefersDark] = useState( + () => window.matchMedia("(prefers-color-scheme: dark)").matches, + ); - return window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; - }, [theme]); + useEffect(() => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleChange = () => setSystemPrefersDark(mediaQuery.matches); + mediaQuery.addEventListener("change", handleChange); + return () => mediaQuery.removeEventListener("change", handleChange); + }, []); + + const systemTheme = useMemo(() => { + if (theme !== "system") return undefined; + return systemPrefersDark ? "dark" : "light"; + }, [theme, systemPrefersDark]); useEffect(() => { //localStorage.removeItem(storageKey); @@ -101,6 +134,8 @@ export function ThemeProvider({ root.classList.add(theme, colorScheme); + updateThemeMetaTags(systemTheme ?? (theme === "system" ? "light" : theme)); + if (systemTheme) { root.classList.add(systemTheme); return;