diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 4048d7b3fb4312049519b03e9c56fd6921219367..86505ff4f5a57489ef7bd549505c67b96ba90e5e 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -6,6 +6,7 @@ import { AuthStatusProvider } from "@/videoag/authentication/AuthStatus";
 import { showErrorToast } from "@/videoag/error/ErrorDisplay";
 import { LanguageProvider } from "@/videoag/localization/LanguageProvider";
 import { ReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
+import { ThemeProvider } from "@/videoag/miscellaneous/Theme";
 import Title from "@/videoag/miscellaneous/TitleComponent";
 import { RealBackendProvider } from "@/videoag/api/ApiProvider";
 import { EditModeProvider } from "@/videoag/object_management/EditModeProvider";
@@ -40,10 +41,12 @@ export default function App({ Component, pageProps }: AppProps) {
                     <AuthStatusProvider>
                         <EditModeProvider>
                             <LanguageProvider>
-                                <ToastContainer />
-                                <DefaultLayout>
-                                    <Component {...pageProps} />
-                                </DefaultLayout>
+                                <ThemeProvider>
+                                    <ToastContainer />
+                                    <DefaultLayout>
+                                        <Component {...pageProps} />
+                                    </DefaultLayout>
+                                </ThemeProvider>
                             </LanguageProvider>
                         </EditModeProvider>
                     </AuthStatusProvider>
diff --git a/src/videoag/miscellaneous/Theme.tsx b/src/videoag/miscellaneous/Theme.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cedb7be7f51d822357077c6b566196909b8d67ae
--- /dev/null
+++ b/src/videoag/miscellaneous/Theme.tsx
@@ -0,0 +1,37 @@
+import type React from "react";
+import { createContext, useContext, useEffect } from "react";
+
+import { useLocalStorageState } from "@/videoag/miscellaneous/Storage";
+
+type ThemeContextType = {
+    theme: string;
+    setTheme: (newValue: string | ((oldValue: string) => string)) => void;
+};
+
+const ThemeContext = createContext<ThemeContextType>({
+    theme: "dark",
+    setTheme: () => {},
+});
+
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
+    const [theme, setTheme] = useLocalStorageState<string>("theme", "dark");
+
+    useEffect(() => {
+        try {
+            let currentTheme = document.documentElement.getAttribute("data-bs-theme");
+            if (theme !== currentTheme) {
+                document.documentElement.classList.add("no-transition");
+                document.documentElement.setAttribute("data-bs-theme", theme);
+                document.documentElement.classList.remove("no-transition");
+            }
+        } catch (e) {
+            console.error("Could not update theme on document", e);
+        }
+    }, [theme]);
+
+    return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
+}
+
+export function useTheme() {
+    return useContext(ThemeContext);
+}
diff --git a/src/videoag/site/DefaultLayout.tsx b/src/videoag/site/DefaultLayout.tsx
index 7c643e5993c7783d4ef64ddeaa08d71929f90557..94e6a7947ed8a92327c2bbe433891bf82c19f3da 100644
--- a/src/videoag/site/DefaultLayout.tsx
+++ b/src/videoag/site/DefaultLayout.tsx
@@ -13,9 +13,9 @@ import UserField from "@/videoag/authentication/UserField";
 import { showErrorToast, ErrorComponent } from "@/videoag/error/ErrorDisplay";
 import { FallbackErrorBoundary } from "@/videoag/error/FallbackErrorBoundary";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
-import { storageGetOrDefault, storageSet } from "@/videoag/miscellaneous/Storage";
 import { ReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
 import { StylizedText } from "@/videoag/miscellaneous/StylizedText";
+import { useTheme } from "@/videoag/miscellaneous/Theme";
 import { useEditMode } from "@/videoag/object_management/EditModeProvider";
 import AnnouncementComponent from "@/videoag/site/AnnouncementComponent";
 import { basePath } from "@/../basepath";
@@ -108,38 +108,9 @@ export function Search({
 
 function NavBar({ status }: { status?: GetStatusResponse }) {
     const [navbarOpen, setNavbarOpen] = useState(false);
+    const { setTheme } = useTheme();
     const { language, setLanguageCode } = useLanguage();
 
-    useEffect(() => {
-        try {
-            let currentTheme = document.documentElement.getAttribute("data-bs-theme");
-            if (storageGetOrDefault("theme", currentTheme) !== currentTheme) {
-                document.documentElement.classList.add("no-transition");
-                document.documentElement.setAttribute(
-                    "data-bs-theme",
-                    storageGetOrDefault("theme", currentTheme),
-                );
-                document.documentElement.classList.remove("no-transition");
-            }
-        } catch (e) {
-            console.error("Could not load theme from local storage", e);
-        }
-    }, []);
-
-    const switchTheme = () => {
-        const theme = document.documentElement.getAttribute("data-bs-theme");
-        if (theme === "dark") {
-            document.documentElement.setAttribute("data-bs-theme", "light");
-        } else {
-            document.documentElement.setAttribute("data-bs-theme", "dark");
-        }
-        try {
-            storageSet("theme", theme === "dark" ? "light" : "dark");
-        } catch (e) {
-            console.error("Could not save theme to local storage", e);
-        }
-    };
-
     const switchLanguage = () => {
         setLanguageCode(language.getCode() === "de" ? "en" : "de");
     };
@@ -267,7 +238,15 @@ function NavBar({ status }: { status?: GetStatusResponse }) {
                                 <UserField isUnavailable={status?.status === "unavailable"} />
 
                                 <div>
-                                    <button className={"btn"} type="button" onClick={switchTheme}>
+                                    <button
+                                        className={"btn"}
+                                        type="button"
+                                        onClick={() =>
+                                            setTheme((oldValue) =>
+                                                oldValue === "dark" ? "light" : "dark",
+                                            )
+                                        }
+                                    >
                                         <span aria-hidden="true" className={"bi bi-moon-stars"} />
                                     </button>
                                 </div>