Dark Mode

Live theme switching with CSS custom properties across every shell component.

.dark class + tokensdark-modetokenscss
Open fullscreen
9:41

In this demo

Nocturne — notes

What to try

  • Flip the toggle in the header
  • Every component re-themes instantly

Code

app.tsx
import { AppShell, Header, Content, MotionProvider } from "appshell-react";
import { framerMotionAdapter } from "appshell-react/motion-framer";
import { Moon, Sun } from "lucide-react";

export default function App() {
  const [isDark, setIsDark] = useState(false);

  useEffect(() => {
    setIsDark(document.documentElement.classList.contains("dark")); // pick up the current theme
  }, []);

  const toggleTheme = () => {
    const next = !isDark;
    setIsDark(next);
    document.documentElement.classList.toggle("dark", next);
  };

  return (
    <MotionProvider adapter={framerMotionAdapter}>
      <AppShell safeArea>
        <Header
          behavior="fixed"
          logo={<span className="font-bold">Nocturne</span>}
          actions={
            <button onClick={toggleTheme} aria-label="Toggle dark mode">
              {isDark ? <Sun className="size-5" /> : <Moon className="size-5" />}
            </button>
          }
        />
        {/* Every component reads the shadcn tokens, so one class flip re-themes everything */}
        <Content className="bg-background text-foreground">{/* notes */}</Content>
      </AppShell>
    </MotionProvider>
  );
}