Requirements for a Color System That Supports Dark Mode
A color system that works in light mode can fall apart the moment the background turns dark. The failure is rarely dramatic. It shows up as a gray label that vanishes against a charcoal card, a blue button that looks confident on white and muddy on black, a red error state that reads as brown. The palette was built for one set of conditions, and dark mode changes those conditions at the level of physics: the eye adapts differently, contrast behaves differently, and colors that looked harmonious on a bright field start to vibrate or sink.
What follows is a set of requirements, in the sense of things a system must satisfy to be considered complete. These are the conditions that separate a palette with a dark theme bolted on from one designed to carry two modes without losing its voice.
Semantic tokens must be the only thing components consume
The first requirement is structural, and it is the one most teams skip because it feels like architecture rather than color. Every component in the system has to reference a semantic token, never a raw hex value. A button does not ask for blue. It asks for the action color, and the system decides what that means in each mode.
This sounds obvious until an audit turns up forty-one hardcoded values scattered across a component library. A border here, a hover state there, a disabled fill that someone eyeballed in Figma at 2 a.m. Each of those is a place where dark mode will break, because each is a decision made under light-mode assumptions and frozen in place.
The requirement is strict: if a color cannot be expressed as a semantic token, the system is incomplete. The token layer is the contract. Components that bypass it are outside the system, whatever the documentation claims.
Contrast ratios have to hold in both modes, measured against the actual background
Contrast is where good intentions go to die. A palette can pass every check in light mode and still produce text that fails in dark mode, because the background the text sits on has changed and nobody re-measured.
The requirement is that every text and background pairing be verified in both modes, against the real surface, not an idealized one. Body text against the base background. Secondary text against a raised card. Placeholder text inside an input. Disabled labels. The small print in a tooltip. Each pairing is a separate measurement, and each has to clear a defined threshold: 4.5 to 1 for normal text, 3 to 1 for large text and for meaningful non-text elements like icons and input borders.
The trap is that dark mode compresses the usable range. On white, a designer has enormous room to go lighter or darker. On near-black, the same text colors that felt restrained in light mode suddenly glare. A mid-gray that read as quiet on white can feel like a shout on black, because the eye is more sensitive to bright elements against dark fields. The palette has to be re-tuned, not inverted.
Elevation must read without relying on shadow
Light mode communicates depth with drop shadows. A card floats above the page because a soft dark blur sits beneath it. In dark mode, that trick collapses. A black shadow on a black background is invisible, and a heavy one just looks like a smudge.
The requirement is that each elevation level has a defined surface color that gets progressively lighter as the element rises. The base background is the darkest. A card sits one step up. A modal or popover sits another step up. The difference between steps needs to be perceptible but quiet, usually a handful of percentage points of lightness.
This is a requirement, not a preference, because without it every layered surface in the product flattens into a single undifferentiated mass. A dropdown menu that cannot be distinguished from the page behind it is a usability failure, and it is one of the most common dark-mode defects in shipped software.
Accent colors need separate dark-mode variants, not the same hues dimmed
A brand blue chosen for light mode is usually too saturated and too dark to work on a dark background. Dropping its lightness makes it muddy. Raising it makes it neon. The hue that felt confident on white can turn shrill on black, and the fix is not a slider.
The requirement is that every accent color have a deliberately chosen dark-mode counterpart, tuned independently. That means adjusting saturation, lightness, and sometimes hue slightly to preserve the intended character. A calm teal may need to lose saturation and gain lightness to stay calm. A warning amber may need to shift toward orange to avoid glowing.
The test is whether the accent still reads as the same color family at a glance. If the light and dark versions look like two different brands, the variant was not tuned, it was guessed.
Status colors must survive the background shift without losing meaning
Error red, success green, warning amber, info blue. These carry meaning, and meaning is the thing most likely to be damaged by a naive dark-mode conversion. A red that reads as clearly wrong on white can read as a dull brick on dark, and a green that signals success can turn into a sickly olive.
The requirement is that each status color be evaluated for both hue clarity and contrast in dark mode, and adjusted until the semantic signal is unambiguous. That often means lighter, slightly desaturated versions with enough luminance to separate from the background while keeping the hue recognizable.
There is a second requirement layered on top: status colors must never be the only signal. A red border alone is not enough, because color perception varies and dark mode can weaken the signal. Icons, text labels, and shape all have to carry the meaning too. The palette supports the message; it does not replace it.
The neutral ramp needs more steps than light mode suggests
Light-mode palettes often get away with a coarse neutral scale: white, a couple of grays, near-black. Dark mode exposes how crude that is. The distance between the base background and the first raised surface is small, and the distance between secondary text and disabled text is smaller still. A five-step ramp cannot cover those gaps with any precision.
The requirement is a neutral ramp with enough steps to place surfaces, borders, and text levels without reusing values. Ten to twelve steps is a reasonable floor. Each step has a defined role, and the roles do not overlap between modes: the token named for a raised surface maps to a lighter value in dark mode and a slightly darker one in light mode, but the semantic name stays fixed while the underlying value changes.
This is where a system earns its keep. When the ramp is granular, a designer adjusting a card in dark mode reaches for the next step rather than inventing a value, and the palette stays coherent.
Images, illustrations, and media need a defined treatment
Color systems tend to stop at UI chrome, but dark mode exposes media as part of the palette problem. A bright product photograph on a dark page glows like a lightbox. A line illustration drawn in near-black disappears entirely. A transparent logo with dark strokes becomes invisible.
The requirement is that the system specify how media behaves in dark mode: a defined overlay opacity for photographs, a set of dark-mode illustration variants or a documented recolor rule, and a logo treatment for each mode. These are not afterthoughts. A page that handles its text and buttons perfectly but leaves a glaring white image in the middle of a dark layout has not solved dark mode.
The mode switch must be a token swap, not a component rebuild
A color system supports dark mode only if switching modes requires nothing from the component layer. The components consume semantic tokens; the mode determines what those tokens resolve to. Toggling the mode should swap the token values and leave every component untouched.
If a single component needs a conditional style to look right in dark mode, the system has failed at the token layer. That conditional is a leak, and leaks multiply. The requirement is that the component tree be mode-agnostic, and that any component that cannot be made mode-agnostic be redesigned rather than patched.
Focus, hover, and active states must be defined for dark surfaces
Interactive states are the most neglected part of any palette, and dark mode makes the neglect visible. A hover state that lightens a button by a few percent works on white and does almost nothing on a dark surface. A focus ring in a mid-blue may vanish against a dark blue button. An active state that darkens a surface can push it below the base background and look like a hole.
The requirement is that every interactive state have an explicit dark-mode definition, tested against the surfaces it actually appears on. Focus rings in particular need to be checked against both the element and the surrounding background, because a ring that fails against either one is not a focus indicator.
A pure black background is a choice with consequences
Many dark modes use near-black rather than true black, and the reason is practical. Pure black maximizes contrast against light text, which sounds good until it produces halation, the slight bloom or smear that some readers perceive around bright text on a black field. Near-black, something in the range of a very dark gray, softens that edge and gives the elevation ramp room to work.
The requirement is that the base background be chosen deliberately and documented, because it anchors every other decision. A system built on true black needs a different elevation strategy and a more restrained text palette than one built on charcoal. Either choice can work. Drifting between them without a decision is how palettes end up inconsistent across a product.
The palette needs to be tested on real content, not swatch grids
A swatch sheet will pass almost any palette. The failures show up in the messy middle of a real screen: a form with labels, helper text, an error message, a disabled submit button, and a tooltip, all on a dark card that sits on a darker page. That composition is where the contrast math, the elevation ramp, and the status colors all meet.
The requirement is that the system be validated against representative screens in both modes before it ships, and that the validation include the awkward states: empty, loading, error, disabled. A palette that only looks right on a marketing page has not been tested where it matters. The test is a real interface doing real work, in the dark, with the lights off.

