Design til mørk tilstand i B2B-kontekster
82% of smartphone users have dark mode enabled. Among developers, it's 91%. Among financial professionals, 73%. The expectation has shifted — dark mode isn't a feature anymore, it's a baseline. Users who spend 8+ hours looking at screens (which describes most B2B software users) actively choose dark mode for reduced eye strain, lower battery consumption on OLED screens, and personal preference.
If your B2B website or application only offers a light theme, you're making a design decision that conflicts with how most of your target audience uses their devices. Here's how to implement dark mode properly.
Why B2B Users Expect Dark Mode
The adoption data across professional tools tells the story:
- GitHub: Dark mode adopted by 72% of active users within the first year
- VS Code: 68% of users use a dark theme (default)
- Slack: Dark mode usage jumped to 65% after implementation
- Figma: 70%+ dark mode adoption
- Linear: Ships dark-first, light mode is the alternative
The pattern: every major tool that B2B professionals use daily offers dark mode, and the majority of users enable it. When a B2B visitor navigates from Slack to Linear to your website, a blinding white page breaks their visual context and feels out of place.
The use cases where dark mode matters most in B2B:
Dashboards and analytics. Users monitoring dashboards for extended periods benefit from reduced eye strain. Dark backgrounds make data visualizations (charts, graphs, metrics) pop with higher perceived contrast.
Developer-facing products. If your customers are engineers, dark mode isn't optional — it's expected. Developer documentation, API references, and code examples look better and are easier to read on dark backgrounds.
Products used in low-light environments. Sales teams working late, operations teams on night shifts, executives reviewing reports on planes. Dark mode makes the product usable without a screen that lights up the entire room.
Color System Architecture
The most common dark mode mistake: inverting colors. Swapping white backgrounds to pure black and black text to pure white creates a harsh, eye-straining experience. Pure black (#000000) backgrounds cause "halation" — bright text appears to bleed into the dark background for users with astigmatism (approximately 33% of the population).
Instead, build two intentional palettes using semantic color tokens:
:root {
/* Light mode tokens */
--color-bg-primary: #ffffff;
--color-bg-secondary: #f5f5f5;
--color-bg-elevated: #ffffff;
--color-text-primary: #111111;
--color-text-secondary: #666666;
--color-border: #e0e0e0;
--color-accent: #0066cc;
}
[data-theme="dark"] {
/* Dark mode tokens — NOT inverted, intentionally designed */
--color-bg-primary: #0f0f0f;
--color-bg-secondary: #1a1a1a;
--color-bg-elevated: #252525;
--color-text-primary: #e0e0e0;
--color-text-secondary: #999999;
--color-border: #333333;
--color-accent: #4d9fff;
}
Key principles for the dark palette:
Use dark gray, not pure black. Background values of #0f0f0f to #1a1a1a feel rich and comfortable. Pure black (#000000) is flat and harsh.
Use off-white, not pure white. Text at #e0e0e0 or #ebebeb reduces contrast to comfortable levels while maintaining readability. Pure white (#ffffff) on dark backgrounds is as straining as black text on white — the contrast is technically maximum, but physiologically uncomfortable.
Elevate surfaces with lighter shades. In light mode, elevated elements (cards, modals, dropdowns) use shadows. In dark mode, shadows are invisible. Instead, use progressively lighter background colors: primary surface → secondary surface → elevated surface. A card on a #0f0f0f background should be #1a1a1a. A modal overlay should be #252525.
Adjust accent colors. A blue that works at 4.5:1 contrast on white (#0066cc) may fail on dark backgrounds. Lighten or saturate accent colors for dark mode. Test contrast ratios for both modes independently.
Implementation Patterns
Three implementation approaches, ranked by quality:
Pattern 1: CSS custom properties + data attribute (recommended).
Use a data-theme attribute on <html> and define all colors as CSS custom properties. Toggle the attribute with JavaScript. This is the most flexible and performant approach.
// Theme toggle with system preference detection
function getTheme(): 'light' | 'dark' {
const stored = localStorage.getItem('theme')
if (stored) return stored as 'light' | 'dark'
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light'
}
function setTheme(theme: 'light' | 'dark') {
document.documentElement.setAttribute('data-theme', theme)
localStorage.setItem('theme', theme)
}
// Initialize on page load (prevent flash)
setTheme(getTheme())
Preventing the flash of wrong theme. The biggest UX problem with dark mode: the page loads in light mode, then JavaScript switches to dark mode — causing a bright flash. Prevent this with an inline script in <head> that runs before rendering:
<script>
(function() {
var t = localStorage.getItem('theme') ||
(matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', t);
})();
</script>
This script is blocking (intentionally) and runs before any CSS is applied, preventing the flash entirely.
Pattern 2: Tailwind CSS dark mode.
Tailwind's built-in dark mode support uses the dark: variant prefix:
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<h1 class="text-black dark:text-white">Heading</h1>
<p class="text-gray-600 dark:text-gray-400">Body text</p>
</div>
Configure Tailwind to use the class strategy (so your toggle script controls it):
// tailwind.config.js
module.exports = {
darkMode: 'class', // or 'selector' for data-attribute
}
This works well but duplicates every color utility. For large codebases, semantic CSS custom properties (Pattern 1) are cleaner because colors are defined once and referenced everywhere.
Pattern 3: System preference only (simplest).
If you don't need a manual toggle, use only the CSS media query:
@media (prefers-color-scheme: dark) {
:root {
--color-bg-primary: #0f0f0f;
--color-text-primary: #e0e0e0;
}
}
This respects the user's OS setting with zero JavaScript. The limitation: users who want light mode on your site but dark mode on their OS (or vice versa) can't override.
Accessibility in Dark Mode
Dark mode introduces specific WCAG compliance challenges:
Contrast ratios must be verified independently. A color combination that passes 4.5:1 in light mode may fail in dark mode (or vice versa). Test every text/background combination in both modes. Common failure: secondary text that passes at #666666 on #ffffff (5.7:1) but fails at #999999 on #0f0f0f (4.2:1 — below the 4.5:1 threshold).
| Element | Light Mode | Dark Mode | Passes? |
|---|---|---|---|
| Primary text | #111 on #fff (19.3:1) | #e0e0e0 on #0f0f0f (14.5:1) | Both pass |
| Secondary text | #666 on #fff (5.7:1) | #999 on #0f0f0f (4.2:1) | Dark fails |
| Secondary text (fixed) | #666 on #fff (5.7:1) | #aaa on #0f0f0f (5.0:1) | Both pass |
Focus indicators need different colors. A blue focus outline that's visible on white backgrounds may be invisible on dark backgrounds. Define focus ring colors per theme:
*:focus-visible {
outline: 2px solid var(--color-focus-ring);
}
:root { --color-focus-ring: #0066cc; }
[data-theme="dark"] { --color-focus-ring: #4d9fff; }
Disabled states must remain distinguishable. In light mode, disabled elements are often grayed out (lower opacity). In dark mode, reducing opacity on already-muted elements can make them invisible. Use explicit disabled colors rather than opacity changes.
Image and icon handling. Some images need adjustment in dark mode. Logos with transparent backgrounds designed for white may be invisible on dark backgrounds. Solutions: provide alternate logo versions per theme, add a subtle background to logo containers, or use CSS filter: invert(1) selectively for simple monochrome graphics.
FAQ
How do I persist user preference across sessions?
localStorage.setItem('theme', 'dark') is the simplest and most reliable approach. For authenticated applications, store the preference in the user profile database so it syncs across devices. For anonymous marketing sites, localStorage is sufficient — the preference persists until the user clears browser data.
Should I default to dark or light?
Default to the user's system preference (prefers-color-scheme media query). If no system preference is detectable, default to light — it's still the majority preference for general web browsing (as opposed to developer tools, where dark dominates). Always provide a toggle so users can override.
How do I handle images in dark mode?
Photos generally look fine on dark backgrounds. Screenshots, diagrams, and charts with white backgrounds look jarring. For critical images, provide dark-mode variants. For user-uploaded content, add a subtle border or rounded background to separate the image from the page. The <picture> element can serve different sources based on color scheme:
<picture>
<source srcset="/chart-dark.png" media="(prefers-color-scheme: dark)" />
<img src="/chart-light.png" alt="Revenue chart" />
</picture>
Does dark mode affect email design? Yes, and it's a nightmare. Email clients (Gmail, Apple Mail, Outlook) each handle dark mode differently — some invert colors automatically, some don't. Gmail's dark mode inverts light backgrounds but leaves dark backgrounds alone. The safest approach: design emails with moderate contrast (dark gray text, light gray backgrounds) that look acceptable in both modes. Test in Litmus or Email on Acid.
What's the development overhead of supporting dark mode? If you set up semantic color tokens from the start, adding dark mode to a new project is ~10% additional design effort and ~5% additional CSS. Retrofitting dark mode onto an existing site with hardcoded colors is significantly more work — potentially 20-40 hours for a typical marketing site.