Dark Mode vs Light Mode — How to Handle Both in a Single DESIGN.md

Dark mode isn’t optional anymore. Users expect it. Operating systems default to it. And if your AI agent generates a component that only works in light mode, you’ve just doubled your review workload.

The tricky part isn’t supporting dark mode. It’s encoding both modes in a DESIGN.md file that stays readable and doesn’t confuse your agent with conditional logic it can’t parse.

I’ve tried several approaches over the past year. Most of them suck. Here’s what actually works.

Why This Is Harder Than It Looks

A naive approach: define two color palettes and tell the agent to use the right one. Problem is, agents don’t have runtime context. They don’t know which mode the user will see. They generate static code. The mode switching happens in CSS or your framework’s theme provider.

So your DESIGN.md needs to describe a system that works in both modes, not two separate systems.

The Semantic Color Approach

Instead of defining colors by their value, define them by their role:

colors:
  # Semantic tokens — these flip between modes
  surface: "var(--surface)"        # white in light, #0f172a in dark
  surface-alt: "var(--surface-alt)" # #f8fafc in light, #1e293b in dark
  text: "var(--text)"              # #0f172a in light, #f1f5f9 in dark
  text-muted: "var(--text-muted)"  # #64748b in both
  border: "var(--border)"          # #e2e8f0 in light, #334155 in dark
  primary: "#2563eb"               # stays the same in both modes
  primary-text: "#ffffff"          # stays the same in both modes

Then in your DESIGN.md prose section, explain the mapping:

## Theming

This design system uses CSS custom properties for mode switching.
The agent should always use semantic token names (surface, text, border),
never raw hex values for backgrounds or text colors.

Light mode values:
- --surface: #ffffff
- --surface-alt: #f8fafc
- --text: #0f172a
- --border: #e2e8f0

Dark mode values:
- --surface: #0f172a
- --surface-alt: #1e293b
- --text: #f1f5f9
- --border: #334155

Primary color (#2563eb) is mode-independent.

The agent generates components using var(--surface) instead of #ffffff. The mode switching happens in CSS, not in the component code. Clean separation.

What About Contrast?

This is where most dark mode implementations break. A color that has 4.5:1 contrast ratio against white might have 2:1 against dark gray. Your DESIGN.md should call this out:

## Do's and Don'ts
- Do test all text colors against both surface values
- Don't use text-muted for small text (< 14px) — insufficient contrast in dark mode
- Do use primary color for interactive elements in both modes (it passes WCAG AA against both surfaces)
- Don't use pure black (#000000) as dark mode surface — too harsh, causes halation

Shadows and Elevation

Shadows behave differently in dark mode. A box-shadow that adds subtle depth on white backgrounds becomes invisible on dark ones. Some approaches:

Option 1: Swap shadows for borders in dark mode. Tell the agent:

## Elevation
- Light mode: use box-shadow for card elevation
- Dark mode: use 1px border with --border color instead of shadows

Option 2: Use lighter shadows in dark mode. Less common but works for glassmorphic styles:

## Elevation
- Light: box-shadow: 0 1px 3px rgba(0,0,0,0.1)
- Dark: box-shadow: 0 1px 3px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.05)

Agent Behavior I’ve Observed

When I give Claude Code a DESIGN.md with semantic tokens and clear mode documentation:

The key insight: agents are good at following token systems. They’re bad at inferring conditional behavior. Be explicit about what changes between modes and what doesn’t.

The Minimal Viable Dark Mode DESIGN.md

If you want to add dark mode support to an existing DESIGN.md without rewriting everything, add this section:

## Dark Mode

This system supports light and dark modes via CSS custom properties.
Always use semantic color names from the tokens above.
Never hardcode background or text colors.

Key differences in dark mode:
- Surfaces get darker (not inverted — use the dark palette, not white-on-black)
- Borders become more visible (lighter opacity)
- Shadows become less visible (use borders for elevation instead)
- Primary accent color stays the same
- Images may need reduced brightness (add opacity: 0.9 in dark mode)

That’s 10 lines. Enough for an agent to handle 90% of cases correctly.

Browse Dark-Ready Design Systems

The DESIGN.md library includes systems designed for both modes:

Pick one that matches your product, then adapt the theming section to your framework.