Designing for Dark Mode First — Lessons from This Site
Why I chose dark-first design for this site, the specific decisions I made around color, contrast, and typography, and what I'd do differently next time.
This site is dark by default. That's not a trend-following choice — it's where I spend most of my time (dark terminals, dark editor, dark everything), so it felt right to design from that direction and add light mode as the variant.
Here's what I learned.
Why Dark-First is Harder
Most design tools, color palette generators, and UI component libraries are optimized for light mode. When you go dark-first, you're fighting the defaults.
The challenges:
Shadows don't work. Box shadows (which create depth in light UIs) are invisible on dark backgrounds. You switch to borders and subtle background elevation instead.
Contrast math is inverted. In light mode, you darken things to add emphasis. In dark mode, you lighten — but too much lightening creates glare. The sweet spot is narrower.
Color vibrancy drops. Saturated colors that look great on white look neon and harsh on dark backgrounds. You need to reduce saturation or use semi-transparent overlays (color/10, color/20).
The Color System I Settled On
:root {
--bg: #0a0a0a; /* near-black, not pure black */
--bg-raised: #111111; /* cards, inputs */
--border: #27272a; /* subtle division */
--text: #fafafa; /* primary text */
--text-muted: #a1a1aa; /* secondary text */
--accent: #22d3ee; /* cyan — visible but not aggressive */
}A few decisions worth unpacking:
#0a0a0a not #000000. Pure black creates too much contrast with everything else and shows panel seams. A very dark gray reads as black but behaves better.
Zinc for neutrals. Tailwind's zinc scale (warm-neutral gray) feels less clinical than slate and less warm than stone. It worked well for a technical site.
Cyan as the accent. I wanted something that reads as "technical" — blue was too corporate, green too Matrix. Cyan (#22d3ee) hits the right note and has good contrast on dark backgrounds without being neon.
Typography
The font stack is Inter for body and headings, JetBrains Mono for code. Not original, but reliable. On dark backgrounds, thinner fonts can disappear — I stick to weight 400 minimum for body, 500+ for anything that needs to stand out.
Line height matters more in dark mode. At 1.7–1.8 line-height, body text feels airy and readable even for longer posts. At 1.5 (the Tailwind default) it starts to feel cramped.
What Trips People Up: Elevation
In light-mode UI, cards "float" above the page because of drop shadows. In dark mode, you create elevation through background lightness, not shadows:
| Level | Background |
|---|---|
| Page | #0a0a0a |
| Card | #111111 |
| Input | #111111 |
| Tooltip | #1f1f1f |
Each step up adds ~10–15 lightness points in HSL. Don't go beyond 3 levels — it gets muddy.
Light Mode as the Variant
When you've designed dark-first, light mode becomes a CSS variable swap:
.light {
--bg: #ffffff;
--bg-raised: #f4f4f5;
--border: #e4e4e7;
--text: #09090b;
--text-muted: #71717a;
}The accent color usually needs adjustment — cyan that looks great on dark can look washed out on white. I kept the same value but it's a thing to watch.
What I'd Do Differently
Start with a proper token system. I defined CSS variables but didn't create a formal Tailwind theme. Halfway through I was mixing var(--bg) with Tailwind color classes. Pick one approach.
Test with real content earlier. Placeholder content hides contrast issues. The first time I dropped in a real MDX post, several things that "looked fine" in the component view were actually hard to read in context.
Don't forget focus states. Dark backgrounds make default browser focus rings nearly invisible. Custom focus styles are not optional.
Dark mode design is genuinely harder to get right than light mode. But building a site in the mode you'll actually look at it in every day is its own reward — you notice and fix things faster.