Design
llms.txt digitalcrew.tech

Navigation

ThemeSwitcher

A round icon button with a Light, Dark and System menu that sets the next-themes theme.

MaxNavigationDigitalCrew.ThemeSwitcher
ThemeSwitcher · liveOpen

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 Sun and a Moon at size-[1.2rem], stacked on top of each other. The dark: variant swaps them: the outgoing icon goes to scale-0 and rotates by 90° while the other scales in, with transition-all.
  • Menu items are Light, Dark and System, each followed by a 14px Check pushed 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#

GroupNavigation
ProductMax
Globalwindow.DigitalCrew.ThemeSwitcher
Sourcemax-agent: src/components/theme-switcher.tsx
Import in the appimport { 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>