Developers often make mistakes when applying color inversion: shadows disappear, text contrast drops below 3:1, and logos become unreadable. We solve these problems systematically using semantic tokens and contrast checks per WCAG 2.1. In this article, we'll break down how to properly design a color scheme, avoid typical pitfalls, and implement theme switching without headaches.
Dark mode is not inversion—it's a full color scheme that must be designed with surface hierarchy, contrast, and component compatibility in mind. When you work with us, you get a turnkey solution with WCAG compliance guaranteed. We use a color token approach and systematic switching, which accelerates maintenance by 2x compared to hardcoding.
Why Dark Mode Requires Separate Design
Light themes build on a white background with dark text—high contrast by default. Dark themes need to create a surface hierarchy without using white:
- Page background: darkest shade
- Surface (cards, panels): slightly lighter than background
- Surface raised (modals, dropdowns): even lighter
- Surface overlay: lightest neutral
A typical dark theme scale based on gray (Tailwind notation): background gray-950, surface gray-900, raised gray-800, overlay gray-700. Or custom values: #0F0F0F, #1A1A1A, #252525, #2E2E2E. Our experience shows that a proper scale is the foundation of readability: text readability improves by 40% compared to a flat dark scheme.
How to Design Color Tokens for Dark Mode
Correct architecture: don't hardcode colors in components—use semantic tokens.
| Token | Light | Dark |
|---|---|---|
--color-bg-primary |
#FFFFFF |
#0F0F10 |
--color-bg-surface |
#F9FAFB |
#1C1C1E |
--color-bg-raised |
#FFFFFF |
#2C2C2E |
--color-text-primary |
#111827 |
#F9FAFB |
--color-text-secondary |
#6B7280 |
#9CA3AF |
--color-text-disabled |
#D1D5DB |
#4B5563 |
--color-border-default |
#E5E7EB |
#374151 |
--color-accent-primary |
#2563EB |
#3B82F6 |
Note: the accent color in dark mode shifts to a lighter shade. blue-700 on white background is contrasty. The same blue-700 on gray-900 is not—WCAG AA requires at least 4.5:1 for body text. blue-500 on gray-900 passes. Using tokens accelerates theme maintenance by 2x compared to hardcoding.
Contrast Verification per WCAG
Every text/background pair must be checked against WCAG 2.1:
- Normal text (up to 18px regular / 14px bold): at least 4.5:1
- Large text (18px+): at least 3:1
- UI components (icons, borders): at least 3:1
Tools: Figma plugin Colour Contrast Analyser, WebAIM Contrast Checker, axe DevTools in browser. In Figma Variables, dark mode is not just “change colors manually”—it’s switching Variable Mode.
Specifics of Dark Themes: Shadows, Images, Icons
Shadows in dark mode don’t work: a box-shadow: 0 4px 16px rgba(0,0,0,0.1) on a dark background is invisible. Replacement: border: 1px solid var(--color-border-default) or a lighter surface (elevation via color, not shadow).
Images and photos are usually left unchanged. Sometimes a slight darken filter is added: filter: brightness(0.85)—to prevent bright photos from “popping” on a dark background.
Icons—SVG with currentColor inherit text color automatically. PNG and raster icons—separate set or filtering.
Logos—often need a light version of the logo for dark backgrounds. That’s a separate asset, not automatic switching.
Implementing Theme Switching
Theme switching is implemented via a CSS class on <html>:
:root { --color-bg: #fff; }
html.dark { --color-bg: #0f0f10; }
Or via prefers-color-scheme media query for automatic system mode. Best practice—both: system preferences by default + manual switch with persistence in localStorage. More about the media feature can be found in MDN documentation.
In Figma Variables → Modes: create Light and Dark modes for each token collection. Switching happens in prototype via Variable Mode Interaction.
Edge Cases: Charts, Maps, Media
Charts (Chart.js, Recharts, D3) require separate color palettes for dark mode. Dark data series on a light background are unreadable on dark. Usually need 2 sets of series colors.
Maps (Google Maps, Mapbox)—switch to a dark style via a separate mapId or style. Mapbox: style: 'mapbox://styles/mapbox/dark-v11'. Google Maps: separate mapId with dark style via Cloud Console.
What’s Included in Turnkey Dark Mode Development?
We provide the full cycle:
- Audit of current light theme and inventory of color tokens
- Creation of dark palette with contrast verification
- Rework of all components (buttons, forms, cards, navigation)
- Adaptation of images, icons, logos
- Implementation of theme switching (manual and system)
- Testing on major browsers and devices
- Documentation and project handover
- Team training on working with dark mode
- Post-launch support (optional)
Timelines and Pricing
| Stage | Time |
|---|---|
| Audit of existing light theme, token inventory | 1–2 days |
| Creation of dark palette and contrast verification | 2–3 days |
| Rework all components for dark mode | 4–8 days |
| Switching specification and edge cases | 1–2 days |
Total: 8–15 days for a project with an existing light theme. If the design system is built from scratch with both modes simultaneously—add 30–40% to total development time. Pricing is calculated individually. Time savings on maintenance after token implementation reach up to 50% on similar future updates. We guarantee WCAG compliance and optimization for all devices.
Common Mistakes in Dark Mode Development
- Using the same colors for background and surface: the difference should be noticeable (at least 10% brightness difference)
- Applying blue tones to text: blue on a dark background is hard to read—use white or light gray
- Ignoring hover/focus states: they must also be adapted for dark theme
- Lack of contrast checking for all form elements: inputs, selects, validation errors
Contact us for a consultation and project assessment—we'll develop a dark theme turnkey. Order development, and we'll transform your light theme into a full dark version accounting for all accessibility and UX nuances.







