diff --git a/frontend/app/layout.js b/frontend/app/layout.js
index 9a448ace..0dce6f86 100644
--- a/frontend/app/layout.js
+++ b/frontend/app/layout.js
@@ -23,6 +23,13 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
+
+
+
diff --git a/frontend/components/Navbar.jsx b/frontend/components/Navbar.jsx
index dac7cf78..7aa93b4e 100644
--- a/frontend/components/Navbar.jsx
+++ b/frontend/components/Navbar.jsx
@@ -3,6 +3,7 @@ import React, { useState } from "react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Menu, X, Gamepad, Trophy, Book, User, Users } from "lucide-react";
+import ThemeToggle from "@/components/ThemeToggle";
const NavigationHeader = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -67,20 +68,23 @@ const NavigationHeader = () => {
- {/* Mobile menu button */}
-
-
+ {/* Theme toggle + mobile menu button */}
+
+
+
+
+
diff --git a/frontend/components/ThemeToggle.jsx b/frontend/components/ThemeToggle.jsx
new file mode 100644
index 00000000..e88f3c56
--- /dev/null
+++ b/frontend/components/ThemeToggle.jsx
@@ -0,0 +1,31 @@
+"use client";
+
+import { Moon, Sun } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { useTheme } from "@/hooks/useTheme";
+
+/**
+ * ThemeToggle
+ * -----------
+ * Light/dark theme switch (#694). Cycles between the two themes and persists
+ * the choice through `useTheme`. Renders a sun or moon icon depending on the
+ * active theme.
+ */
+export default function ThemeToggle({ className }) {
+ const { theme, toggleTheme } = useTheme();
+ const isDark = theme === "dark";
+
+ return (
+
+ );
+}
diff --git a/frontend/hooks/useTheme.js b/frontend/hooks/useTheme.js
new file mode 100644
index 00000000..2c8394d2
--- /dev/null
+++ b/frontend/hooks/useTheme.js
@@ -0,0 +1,69 @@
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+
+/**
+ * localStorage key holding the user's explicit theme choice.
+ */
+export const THEME_STORAGE_KEY = "stellarhunts.theme";
+
+/**
+ * useTheme
+ * --------
+ * Light/dark theme state for the app (#694).
+ *
+ * - Resolves the initial theme from the persisted choice, falling back to
+ * the system `prefers-color-scheme` preference on first visit.
+ * - Applies `dark` / `light` classes to `document.documentElement`, matching
+ * the Tailwind `darkMode: ["class"]` configuration.
+ * - Persists an explicit toggle choice in `localStorage`.
+ *
+ * @returns {{ theme: "light"|"dark", setTheme: Function, toggleTheme: Function }}
+ */
+export function useTheme() {
+ const [theme, setThemeState] = useState("light");
+
+ // On mount, read the persisted choice or the system preference. The inline
+ // script in the root layout has already applied the class before hydration,
+ // so this only syncs React state with what is on the document.
+ useEffect(() => {
+ if (typeof window === "undefined") return;
+
+ const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
+ if (stored === "light" || stored === "dark") {
+ setThemeState(stored);
+ return;
+ }
+
+ const prefersDark =
+ window.matchMedia &&
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
+ setThemeState(prefersDark ? "dark" : "light");
+ }, []);
+
+ const applyTheme = useCallback((next) => {
+ if (typeof document === "undefined") return;
+ const root = document.documentElement;
+ root.classList.toggle("dark", next === "dark");
+ root.classList.toggle("light", next === "light");
+ }, []);
+
+ const setTheme = useCallback(
+ (next) => {
+ setThemeState(next);
+ applyTheme(next);
+ if (typeof window !== "undefined") {
+ window.localStorage.setItem(THEME_STORAGE_KEY, next);
+ }
+ },
+ [applyTheme]
+ );
+
+ const toggleTheme = useCallback(() => {
+ setTheme(theme === "dark" ? "light" : "dark");
+ }, [theme, setTheme]);
+
+ return { theme, setTheme, toggleTheme };
+}
+
+export default useTheme;