Dark Mode Theme System

ZeroDrag includes a professional dark mode using shadcn/ui's token-based theming.

What This System Does

The theme system provides:

  • CSS variables - No hardcoded colors
  • Automatic adaptation - All components respond to theme changes
  • Persistent - Theme saved in localStorage
  • System-aware - Respects user's OS preference
  • Accessible - Proper contrast ratios

Why It Exists

Dark mode is expected in modern applications. This system provides a production-ready theming solution that works across all components automatically, with proper contrast and accessibility.

When You Need to Care About It

You'll interact with theming when:

  • Customizing colors for your brand
  • Adding new components that need theme support
  • Creating custom color schemes
  • Fixing theme-related styling issues

Architecture

Color Variables (`app/globals.css`)

All colors use OKLCH color space for perceptual uniformity:

css
:root {
  --background: oklch(1 0 0); /* Light: White */
  --foreground: oklch(0.145 0 0); /* Light: Near black */
  --primary: oklch(0.205 0 0); /* Primary action */
}

.dark {
  --background: oklch(0.145 0 0); /* Dark: Dark gray */
  --foreground: oklch(0.985 0 0); /* Dark: Near white */
  --primary: oklch(0.922 0 0); /* Primary inverted */
}

Theme Provider (`ThemeProvider.tsx`)

Wraps your app in layout.tsx:

tsx
import { ThemeProvider } from "@/components/ThemeProvider";

<ThemeProvider>{children}</ThemeProvider>;

Theme Toggle (`ThemeToggle.tsx`)

Add to any component:

tsx
import { ThemeToggle } from "@/components/ThemeToggle";

<ThemeToggle />;

Color Tokens

Always use these semantic tokens instead of hardcoded colors:

TokenUsageExample
backgroundPage backgroundbg-background
foregroundPrimary texttext-foreground
cardCard backgroundbg-card
mutedMuted backgroundsbg-muted
primaryPrimary buttonsbg-primary
borderBordersborder-border

Usage

✅ Correct Usage

tsx
// Good - uses theme tokens
<div className="bg-background text-foreground border-border">
  <p className="text-muted-foreground">Muted text</p>
</div>

<Card>
  <CardTitle>Automatically themed</CardTitle>
</Card>

❌ Avoid

tsx
// Bad - hardcoded colors won't adapt
<div className="bg-white text-gray-900">
  <p className="text-gray-500">Text</p>
</div>

Migration Guide

Replace hardcoded colors with semantic tokens:

  • bg-whitebg-background or bg-card
  • text-gray-900text-foreground
  • text-gray-500text-muted-foreground
  • border-gray-200border-border
  • bg-gray-100bg-muted

Customization

Change Colors

Edit app/globals.css:

css
:root {
  --primary: oklch(0.5 0.15 240); /* Blue */
}

.dark {
  --primary: oklch(0.7 0.15 240); /* Lighter blue */
}

Tools:

Add Custom Colors

1. Add to both `:root` and `.dark`:

css
:root {
  --my-color: oklch(0.7 0.2 50);
}
.dark {
  --my-color: oklch(0.6 0.2 50);
}

2. Register in `@theme inline`:

css
@theme inline {
  --color-my-color: var(--my-color);
}

3. Use in components:

tsx
<div className="bg-my-color">Content</div>

Advanced Features

Force Theme Programmatically

typescript
import { useTheme } from "next-themes";

const { setTheme } = useTheme();
setTheme("dark"); // or "light"

Per-Section Themes

tsx
<div className="dark">
  <Card>Always dark</Card>
</div>

Add More Themes

1. Add CSS class:

css
.midnight {
  --background: oklch(0.05 0 0);
  --foreground: oklch(0.95 0 0);
}

2. Update ThemeProvider:

tsx
themes={["light", "dark", "midnight"]}

Troubleshooting

Hydration Mismatch

Ensure suppressHydrationWarning on <html>:

tsx
<html lang="en" suppressHydrationWarning>

Colors Not Changing

  1. Check for hardcoded colors (bg-white vs bg-background)
  2. Verify .dark class on <html> element
  3. Debug with:
    typescript
    const { theme, resolvedTheme } = useTheme();
    console.log({ theme, resolvedTheme });

File Structure

Core Files (🔒 DO NOT MODIFY):

  • app/globals.css - Color variables
  • components/ThemeProvider.tsx - Theme context
  • components/ThemeToggle.tsx - Toggle button
  • app/layout.tsx - Wraps app

Editable Files (🏗️ SAFE TO EDIT):

  • components/Header.tsx - UI placement
  • components/landing/LandingNav.tsx - UI placement

Related Sections