On this page
Dark mode changes more than the background. It changes which surfaces appear elevated, how strongly accent colors attract attention, and how comfortably a dense screen can be scanned in a particular environment.
A useful dark theme is not an inverted screenshot. It is a second presentation of the same hierarchy, tested with real content and user preferences. Build that presentation from semantic colors, measured contrast, and explicit interaction states.
Separate preference from universal claims
Users choose dark interfaces for different reasons: reduced brightness in a dim room, familiarity with another application, aesthetic preference, or comfort during a particular task. None of these establishes that dark mode is universally easier to read or medically protective.
People, displays, and lighting conditions differ. Bright text on a dark surface can look crisp to one reader and appear to spread or blur to another. Treat user choice as the practical response rather than declaring a single theme superior.
Use the UI Contrast & WCAG Accessibility Checker below to test actual foreground and background pairs. A theme that feels subdued still needs readable text and recognizable controls. A contrast ratio is evidence about one color relationship, not a complete measure of comfort.
Write down the product's priorities before choosing shades. A code editor, a trading dashboard, and a reading app place different demands on density, sustained attention, and color meaning. Those tasks should guide the theme more than a fashionable reference image.
Build hierarchy with semantic surfaces
Start with surface roles instead of a list of unrelated grays. Define a page background, card surface, elevated surface, primary text, muted text, border, and accent. Components should reference those roles rather than hardcoding individual hex values.
Near-black backgrounds can provide room for visible surface differences without requiring every card to cast a strong shadow. Slightly lighter surfaces can suggest elevation, but this is a design convention, not a rule that overrides usability testing.
Keep structural separation visible. A subtle border or tonal step can distinguish a dropdown from its surroundings when shadows are difficult to perceive. Test the open dropdown against both the page background and the card beneath it.
Use the Tailwind CSS Palette Generator to explore related shades, then assign them to semantic roles deliberately. A numbered shade scale does not automatically tell you which pair passes contrast or which surface should appear raised.
Avoid changing every token at once during review. Establish the background and main text first, add surfaces second, and evaluate accents last. This sequence makes it easier to identify the relationship responsible for a weak hierarchy.
Measure contrast without flattening the interface
For WCAG AA, normal text generally needs at least 4.5:1 contrast, while large text needs at least 3:1. Large text means at least 18 points, or 14 points when bold, approximately 24 CSS pixels or 18.67 CSS pixels respectively.
The WAI contrast minimum guidance explains the requirement and its exceptions. Do not classify an ordinary 16-pixel semibold label as large text just because it looks prominent in a mockup.
Necessary visual information identifying controls and their states generally needs non-text contrast of at least 3:1 against adjacent colors under the applicable criterion. Decorative separators do not automatically carry that requirement. Consult the WAI non-text contrast guidance when distinguishing decoration from functional boundaries.
Muted text still needs to be readable when it communicates meaningful information. Reserve low-emphasis styling for genuinely secondary content, and do not use reduced opacity as an excuse to make timestamps or helper instructions inaccessible.
Ratios must reflect the rendered colors. Alpha transparency blends with the background, so a translucent white label over two different surfaces produces two different pairs. Test each relevant combination rather than checking an isolated token once and assuming the entire system passes.
Implement preferences without losing manual control
Support the operating system preference, but let an explicit theme override win when the product offers one. A three-state setting, system, light, or dark, communicates this behavior more clearly than an ambiguous toggle that forgets its selection.
The following CSS applies dark tokens for an explicit dark selection and for an unconfigured page when the system requests dark mode. Shared custom properties keep the actual component rules independent of the preference mechanism.
:root {
color-scheme: light;
--page: #ffffff;
--surface: #f3f4f6;
--text: #18202b;
--muted: #4b5563;
}
:root[data-theme="dark"] {
color-scheme: dark;
--page: #111827;
--surface: #1f2937;
--text: #f3f4f6;
--muted: #cbd5e1;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
color-scheme: dark;
--page: #111827;
--surface: #1f2937;
--text: #f3f4f6;
--muted: #cbd5e1;
}
}
body {
background: var(--page);
color: var(--text);
}
.card {
background: var(--surface);
}The color-scheme property helps native controls and other browser-rendered elements match the intended presentation. It does not automatically recolor custom component styles. The MDN color-scheme reference explains that distinction.
Persist only explicit user choices. For the system option, remove the override and let the media query respond. Apply any stored preference early enough to avoid a distracting flash, while keeping the implementation compatible with the application's rendering and security model.
Review accents, states, and real environments
A saturated accent can feel much more intense against dark surroundings. Use measured emphasis: reserve strong color for primary actions and important information, then test links, badges, charts, and alerts separately. One brand color rarely works unchanged for every role.
Do not rely on hue alone to communicate status. Pair errors with text and recognizable indicators, and give selected navigation items a non-color cue. A red and green chart needs labels or another differentiator regardless of whether its colors look vivid.
Run state-based checks in the contrast checking tool, including hover, focus, selected, error, and read-only appearances. Disabled controls have specific exceptions, but they should still look intentionally disabled rather than accidentally missing.
Review a representative workflow on a dim screen and in a bright room. Include dense text, images with white backgrounds, charts, overlays, and native form controls. Capture the problems as token or component changes, not a collection of page-specific color patches. Include at least one participant who normally prefers the opposite theme; their feedback can reveal assumptions that an enthusiastic dark-mode reviewer might overlook.
For broader interaction testing, use the practical accessibility guide alongside these color checks. Keyboard access, meaningful labels, and sensible reading order remain essential in both themes.
Key takeaways
- Respect individual preference rather than promising that one theme suits everyone.
- Build semantic surface roles before choosing accent colors and decorative effects.
- Measure text and functional non-text contrast against the actual adjacent surfaces.
- Honor system settings while preserving an explicit manual override.
- Test every interaction state, not just the default screen, and pair color with other meaningful cues.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor