Navigation
ThemeSwitcher
A round icon button with a Light, Dark and System menu that sets the next-themes theme.
Guidelines#
Use it for the app header, next to the notification bell. It takes no props.
Behaviour: calls setTheme("light" | "dark" | "system") from useTheme() (next-themes, through @/contexts/theme-provider) and ticks the active item. When the resolved theme changes it updates <meta name="theme-color"> to #090706 for dark and #ffffff for light. The menu is non-modal (modal={false}) and aligned to the end.
Anatomy:
- A ghost icon
Button,scale-95 rounded-full. - The icon is a
Sunand aMoonatsize-[1.2rem], stacked on top of each other. Thedark:variant swaps them: the outgoing icon goes toscale-0and rotates by 90° while the other scales in, withtransition-all. - Menu items are Light, Dark and System, each followed by a 14px
Checkpushed right (ms-auto) and hidden unless active.
In this preview: it sits in its own next-themes provider (DC.NextThemesProvider) that writes to a harmless attribute. Choosing an item ticks it but leaves the page's theme alone. The sun or moon follows the page's own dark variant.
Don't use it outside the app's ThemeProvider, where setTheme does nothing. Don't add more themes to its menu: the rest of the appearance options live in Settings › Appearance.
Facts#
| Group | Navigation |
|---|---|
| Product | Max |
| Global | window.DigitalCrew.ThemeSwitcher |
| Source | max-agent: src/components/theme-switcher.tsx |
| Import in the app | import { ThemeSwitcher } from "@/components/theme-switcher"; |
API#
TypeScript declarations emitted from src/components/theme-switcher.tsx.
export declare function ThemeSwitcher(): JSX.Element;Example#
The code of the preview above: plain React.createElement against window.DigitalCrew, with realistic data.
<div id="root" class="p-6"></div>
<script>
(function () {
var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
function App() {
var ref = React.useRef(null);
React.useEffect(function () {
// Open the menu once so the card shows it; Radix opens on a primary-button pointerdown.
var t = setTimeout(function () {
var btn = ref.current && ref.current.querySelector("button");
if (btn) btn.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true, button: 0, pointerType: "mouse" }));
}, 150);
return function () { clearTimeout(t); };
}, []);
return h("div", { className: "flex justify-end" },
h("div", { ref: ref, className: "flex items-center gap-1 rounded-full border border-glass bg-background/70 px-2 py-1" },
h("span", { className: "px-2 text-xs text-muted-foreground" }, "Theme"),
h(DC.ThemeSwitcher)));
}
ReactDOM.createRoot(document.getElementById("root")).render(
// Its own next-themes provider, writing to a harmless attribute: picking an item ticks it without re-theming this page.
h(DC.NextThemesProvider, { attribute: "data-nt", storageKey: "ds-theme-switcher-demo", defaultTheme: "system", enableColorScheme: false },
h(DC.TooltipProvider, { delayDuration: 80 }, h(App))));
})();
</script>