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:
- It consistently uses
var(--surface)instead of hardcoded colors - It adds
dark:variants in Tailwind when the DESIGN.md mentions Tailwind - It occasionally forgets to handle shadows differently per mode (needs explicit reminder)
- It never generates mode-toggle logic unless asked — it just makes components that work in both
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:
- Full dark/light support: look for ”✓ Full / ✓ Full” in the Light/Dark field
- Dark-only systems: Cyber-Tribal, Steampunk Vitoriano
- Light-only systems: Minimalism & Swiss Style, Art Nouveau Florido
Pick one that matches your product, then adapt the theming section to your framework.