diff --git a/src/layouts/bots/(partials)/rail-header.tsx b/src/layouts/bots/(partials)/rail-header.tsx
index a958023..183284b 100644
--- a/src/layouts/bots/(partials)/rail-header.tsx
+++ b/src/layouts/bots/(partials)/rail-header.tsx
@@ -3,6 +3,7 @@ import Link from "@moduletrace/twui/layout/Link";
import Wordmark from "@/src/components/general/wordmark";
import LogoIcon from "@/src/components/general/logo-icon";
import RailAddButton from "./rail-add-button";
+import RailThemeToggle from "./rail-theme-toggle";
import { twMerge } from "tailwind-merge";
import { useContext } from "react";
import { AppContext } from "@/src/pages/__root";
@@ -47,7 +48,10 @@ export default function RailHeader({ show_add = true }: Props) {
>
)}
- {show_add ? : null}
+
+
+ {show_add ? : null}
+
);
}
diff --git a/src/layouts/bots/(partials)/rail-theme-toggle.tsx b/src/layouts/bots/(partials)/rail-theme-toggle.tsx
new file mode 100644
index 0000000..b785ea5
--- /dev/null
+++ b/src/layouts/bots/(partials)/rail-theme-toggle.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import Button from "@moduletrace/twui/layout/Button";
+import { Moon, Sun } from "lucide-react";
+import useThemePreference from "@/src/hooks/use-theme-preference";
+import resolveTheme from "@/src/utils/resolve-theme";
+
+const DarkQuery = "(prefers-color-scheme: dark)";
+
+/**
+ * The rail header's scheme toggle: one click flips between light and dark
+ * for this device, stored where `ThemeInitScript` reads it. Following the
+ * device stays on Settings > General, since a toggle is always an explicit
+ * choice. It renders after mount only, because the painted scheme is
+ * decided on the client.
+ */
+export default function RailThemeToggle() {
+ const { preference, setPreference } = useThemePreference();
+ const [mounted, setMounted] = useState(false);
+ const [system_is_dark, setSystemIsDark] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+
+ const media = window.matchMedia(DarkQuery);
+ const onChange = () => setSystemIsDark(media.matches);
+
+ onChange();
+ media.addEventListener("change", onChange);
+ return () => media.removeEventListener("change", onChange);
+ }, []);
+
+ if (!mounted) return null;
+
+ const is_dark =
+ (preference
+ ? resolveTheme({ preference, system_is_dark })
+ : system_is_dark
+ ? "dark"
+ : "light") == "dark";
+ const title = is_dark ? "Switch to the light theme" : "Switch to the dark theme";
+ const Icon = is_dark ? Moon : Sun;
+
+ return (
+
+ );
+}