Dark Mode

Maddex UI includes built-in support for dark mode using Tailwind CSS v4 and CSS variables. By toggling a single class on the html element, your entire application seamlessly transitions between color schemes.

CSS Variables

We define semantic colors (like --background) in :root. When the .dark class is applied, we override these variables with their dark-mode counterparts.

Tailwind v4

Maddex is configured with a custom variant @custom-variant dark (&:is(.dark *));. This allows you to style elements specifically for dark mode using the dark: prefix if needed.

1. CSS Configuration

Ensure your globals.css defines the color palette for both light and dark modes. Maddex relies on oklch colors for consistent perceptual brightness.

globals.css
@import "tailwindcss";

/* 1. Define the custom variant */
@custom-variant dark (&:is(.dark *));

/* 2. Light Mode Defaults */
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  /* ... other variables */
}

/* 3. Dark Mode Overrides */
.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
}

2. The Toggle Component

We use PulsePoint state to manage the theme. This ensures the UI is reactive and the selection persists via localStorage. The effect hook automatically updates the html class list.

components/theme_toggle.html
<!-- Toggle Button UI -->
<button
  class="rounded-full bg-muted p-2 hover:bg-accent hover:text-accent-foreground"
  aria-label="Toggle theme"
  onclick="toggleThemeMode()"
>
  <Sun class="size-4" hidden="{toggleTheme === 'dark'}" />
  <Moon class="size-4" hidden="{toggleTheme === 'light'}" />
</button>

<!-- PulsePoint Logic -->
<script>
  const [toggleTheme, setToggleTheme] = pp.state(
    localStorage.getItem("maddex_theme") || "dark"
  );

  // Sync DOM with State
  pp.effect(() => {
    if (toggleTheme === "dark") {
      document.documentElement.classList.add("dark");
    } else {
      document.documentElement.classList.remove("dark");
    }
  }, [toggleTheme]);

  // Toggle Handler
  function toggleThemeMode() {
    if (toggleTheme === "light") {
      setToggleTheme("dark");
      localStorage.setItem("maddex_theme", "dark");
    } else {
      setToggleTheme("light");
      localStorage.setItem("maddex_theme", "light");
    }
  }
</script>

Best Practices

  • Use Semantic Colors: Always use bg-background and text-foreground. Avoid hardcoding generic colors like bg-white or text-black.
  • Default State: Initialize the state with a fallback to "system" preference if local storage is empty (optional enhancement).
  • Avoid Flash of Unstyled Content: Ensure your base template runs a small script to check localStorage before the body renders to prevent flickering.