Dark Mode

All landing components support dark mode via Tailwind's dark: prefix.

How It Works

The theme is controlled by ThemeProvider which adds a dark class to the HTML element:

// src/contexts/ThemeContext.tsx
export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    document.documentElement.classList.toggle('dark', theme === 'dark');
  }, [theme]);

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

Using Dark Mode Classes

Apply dark mode styles using Tailwind's dark: prefix:

// Background colors
className="bg-white dark:bg-zinc-950"

// Text colors
className="text-zinc-900 dark:text-white"

// Secondary text
className="text-zinc-600 dark:text-zinc-400"

// Borders
className="border-zinc-200 dark:border-zinc-800"

// Backgrounds with opacity
className="bg-zinc-100 dark:bg-zinc-900/50"

Color Palette

Use the zinc palette for consistent dark mode:

Light Mode Dark Mode Usage
bg-white bg-zinc-950 Page background
bg-zinc-50 bg-zinc-900 Section background
bg-zinc-100 bg-zinc-800 Card background
text-zinc-900 text-white Primary text
text-zinc-600 text-zinc-400 Secondary text
border-zinc-200 border-zinc-800 Borders

Configuration

Tailwind Config

Ensure dark mode is class-based in tailwind.config.js:

module.exports = {
  darkMode: 'class',
  // ...
};

Root Layout

Wrap your app with ThemeProvider:

// src/app/layout.tsx
import { ThemeProvider } from '@/contexts/ThemeContext';

export default function RootLayout({ children }) {
  return (
    <html suppressHydrationWarning>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

CSS Variables

Add dark mode color scheme:

/* globals.css */
.dark {
  color-scheme: dark;
}

Theme Toggle

Use the useTheme hook for toggle buttons:

import { useTheme } from '@/contexts/ThemeContext';
import { SunIcon, MoonIcon } from '@/components/icons';

function ThemeToggle() {
  const { theme, setTheme } = useTheme();

  return (
    <button
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
      aria-label="Toggle theme"
    >
      {theme === 'dark' ? <SunIcon /> : <MoonIcon />}
    </button>
  );
}

Common Patterns

Gradient Backgrounds

// Light: blue gradient, Dark: zinc gradient
className="bg-gradient-to-b from-zinc-50 to-zinc-100 dark:from-zinc-900 dark:to-zinc-950"

Cards with Borders

className="bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl"

Hover States

className="hover:bg-zinc-100 dark:hover:bg-zinc-800"

Inverted Text on Dark Backgrounds

// For sections with dark backgrounds in both modes
className="bg-zinc-900 text-white dark:bg-zinc-800"

Troubleshooting

Dark mode not applying

  1. Check ThemeProvider wraps your app
  2. Verify darkMode: 'class' in tailwind.config.js
  3. Ensure .dark class is on <html> element

Flash of wrong theme

Add suppressHydrationWarning to html tag and initialize theme before render:

<html suppressHydrationWarning>

Images look wrong in dark mode

Use different images or apply filters:

className="dark:invert dark:brightness-0"

Or provide alternate images:

<Image src={isDark ? '/logo-dark.svg' : '/logo-light.svg'} />