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;