Claude Skills Guide

Claude Code Dark Mode Implementation Guide

Dark mode has transformed from a nice-to-have feature into an expectation for modern web applications. Users appreciate the flexibility to switch between light and dark themes, whether for aesthetic preferences, reduced eye strain in low-light environments, or accessibility considerations. This guide walks you through implementing dark mode systematically using Claude Code, covering CSS custom properties, JavaScript toggles, system preference detection, and persistence strategies.

CSS Custom Properties as the Foundation

The most maintainable approach to dark mode relies on CSS custom properties (variables). Instead of maintaining separate stylesheets or complex class-based overrides, you define your color palette once and swap values based on a data attribute or class on the root element.

:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f5f5f5;
  --text-primary: #1a1a1a;
  --text-secondary: #666666;
  --border-color: #e0e0e0;
  --accent-color: #0066cc;
}

[data-theme="dark"] {
  --bg-primary: #1a1a1a;
  --bg-secondary: #2d2d2d;
  --text-primary: #f0f0f0;
  --text-secondary: #a0a0a0;
  --border-color: #404040;
  --accent-color: #4da6ff;
}

body {
  background-color: var(--bg-primary);
  color: var(--text-primary);
  transition: background-color 0.3s ease, color 0.3s ease;
}

This pattern scales well because you update component styles to use the custom properties rather than hardcoded colors. When you need to modify your theme, you change the values in one place.

Implementing the Theme Toggle

A theme toggle requires both JavaScript logic and UI elements. The toggle should persist user preference, respect system preferences on first visit, and update immediately without a flash of unstyled content.

const getThemePreference = () => {
  const stored = localStorage.getItem('theme');
  if (stored) return stored;
  return window.matchMedia('(prefers-color-scheme: dark)').matches 
    ? 'dark' 
    : 'light';
};

const setTheme = (theme) => {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
};

const toggleTheme = () => {
  const current = document.documentElement.getAttribute('data-theme');
  setTheme(current === 'dark' ? 'light' : 'dark');
};

document.addEventListener('DOMContentLoaded', () => {
  setTheme(getThemePreference());
});

You can invoke Claude Code with the frontend-design skill to generate accessible toggle buttons that follow WAI-ARIA patterns:

/frontend-design create accessible dark mode toggle button with proper ARIA labels

Preventing Flash of Wrong Theme

A common issue occurs when the page loads before JavaScript executes—the user sees a flash of the wrong theme. The solution involves a small inline script in the <head> that runs before the page renders:

<head>
  <script>
    (function() {
      const stored = localStorage.getItem('theme');
      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      const theme = stored || (prefersDark ? 'dark' : 'light');
      document.documentElement.setAttribute('data-theme', theme);
    })();
  </script>
</head>

This script executes synchronously before any CSS loads, ensuring the correct theme is applied immediately.

Handling System Preference Changes

Users may change their system preference while using your application. Listening for changes keeps your implementation in sync:

const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

mediaQuery.addEventListener('change', (e) => {
  if (!localStorage.getItem('theme')) {
    setTheme(e.matches ? 'dark' : 'light');
  }
});

This code only applies system preference changes when the user hasn’t manually set a preference. Once a user toggles manually, their choice takes priority.

Component-Level Dark Mode

Larger applications benefit from component-level theming. Each component defines its own custom properties, allowing granular control:

.card {
  background-color: var(--card-bg, var(--bg-secondary));
  border: 1px solid var(--card-border, var(--border-color));
  border-radius: 8px;
  padding: 16px;
}

[data-theme="dark"] .card {
  --card-bg: #252525;
  --card-border: #3a3a3a;
}

The tdd skill can help you write tests for component theming to ensure consistency across your design system:

/tdd create test suite for dark mode component styling consistency

Dark Mode for Syntax Highlighting

Code blocks require special attention in dark mode. If you use Prism.js or Highlight.js, most themes include dark variants:

// Apply dark code theme
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/prism-tomorrow.css';
document.head.appendChild(link);

For custom code blocks, define syntax colors as CSS variables:

.code-block {
  --code-keyword: #c678dd;
  --code-string: #98c379;
  --code-comment: #5c6370;
  --code-function: #61afef;
}

[data-theme="light"] .code-block {
  --code-keyword: #a626a4;
  --code-string: #50a14f;
  --code-comment: #a0a1a7;
  --code-function: #4078f2;
}

Testing Dark Mode Implementation

Automated testing ensures your dark mode works correctly across different scenarios. The pdf skill can help generate accessibility reports for your themed interfaces:

/pdf create accessibility audit for dark mode contrast ratios

For visual regression testing, consider tools like Playwright or Percy that capture screenshots in both themes and flag unintended changes.

Best Practices Summary

Keep these principles in mind as you implement dark mode: use CSS custom properties for maintainability, always persist user preferences, prevent flash of wrong theme with inline scripts, respect system preferences on first visit, test both themes thoroughly, and ensure sufficient color contrast for accessibility.

Dark mode implementation doesn’t have to be complicated. By building on CSS custom properties and following these patterns, you create a flexible theming system that serves all users regardless of their preference or device settings.

Built by theluckystrike — More at zovo.one