Color Palette Generator
GeneratorPalettesBrandsFrom ImageGuidesAPI
X/TwitterPrivacyTerms
  1. Home
  2. /Guides
  3. /Dark Mode Color Systems: A Complete Design Guide

Dark Mode Color Systems: A Complete Design Guide

11 min read · March 1, 2026

Dark mode is now a baseline user expectation. Over 80% of smartphone users enable it when available, and in developer and design tools the percentage is even higher. But dark mode done poorly is worse than no dark mode at all: washed-out text, glowing borders, colors that look fine in light mode but vibrate painfully against dark backgrounds.

This guide covers how to design a dark mode color system that is genuinely polished — not just your light palette inverted. You will learn why simple inversion fails, how to construct proper surface hierarchies, which colors need to change in dark mode and by how much, and how to validate your system before shipping.

Why You Cannot Simply Invert Your Light Palette

The intuitive approach to dark mode is to flip everything: white becomes black, black becomes white, your blue stays your blue. It fails for three interconnected reasons.

1. Perceptual lightness is nonlinear

Human vision does not perceive brightness linearly. A gray that provides sufficient separation from white in light mode may disappear against a dark background — not because the hex values are wrong, but because the perceptual contrast at that end of the lightness scale is compressed.

Colors need to be rebased, not flipped. A border that works at hsl(220 13% 85%) on a white background needs to be recalculated for a hsl(222 47% 11%) background — and the result is not hsl(220 13% 15%).

2. Saturation reads differently on dark surfaces

Highly saturated colors on dark backgrounds create a phenomenon called "blooming" — the color appears to glow and bleed into surrounding pixels. A brand blue that reads as clean and professional on white can look aggressive or neon on dark surfaces.

Most successful dark mode implementations reduce chroma/saturation on brand colors and compensate with slightly increased lightness. Discord's blurple sits at roughly 60% saturation on dark surfaces — noticeably lower than their light-mode use — so it reads as a clean, confident color rather than a glowing accent.

3. Semantic meanings shift

Green as a success color works on light backgrounds because we associate it with natural, positive signals. On dark backgrounds, a fully saturated green reads differently — more urgent, even alarming, depending on the surrounding context. Your semantic colors (success, warning, error, info) need to be validated fresh in dark contexts, not just inherited.

The Surface Hierarchy Model

Light mode creates visual hierarchy through shadow (elements lift away from a white background). Dark mode needs a different mechanism: surface brightness. Elements that are "closer" to the user (modals, popovers, cards) are lighter than the base background.

The Three-Layer Stack

A minimal dark mode surface system needs at least three distinct surface levels:

[data-theme="dark"] {
  /* Base: the page background */
  --surface-base: hsl(222 47% 8%);

  /* Raised: cards, panels, sidebars */
  --surface-raised: hsl(222 47% 12%);

  /* Overlay: modals, dropdowns, tooltips */
  --surface-overlay: hsl(222 47% 16%);

  /* Sunken: inputs, code blocks, inset areas */
  --surface-sunken: hsl(222 47% 6%);
}

The lightness increments matter. A 4% lightness step between surface levels is typically enough for clear differentiation without the stepped layers looking garish. Go too wide (10%+) and your dark mode starts looking like a high-contrast accessibility mode, not a polished design.

Look at dark palettes to see how well-constructed dark systems balance surface depth without feeling harsh.

Try it yourself

“dark mode color system with multiple surface levels and brand accent”

Generate

Why GitHub's Dark Mode Works

GitHub's dark mode is worth studying. Their base surface is #0d1117 — not pure black, but a very deep blue-black. Cards and panels sit at #161b22. The border color is #30363d. These increments are subtle but consistent.

The result is a surface hierarchy that communicates depth without high contrast between levels. The content — code, text, diffs — gets all the contrast; the chrome disappears into graduated depth. Their branded green (#3fb950 in dark mode vs #1a7f37 in light mode) is lighter and less saturated in dark contexts, preventing it from blooming.

Discord takes a slightly warmer approach — their dark surfaces carry a subtle purple tint that reinforces their brand hue even at the lowest saturation level. This is the right call: your darkest backgrounds can still carry a trace of brand DNA.

Designing Your Dark Palette Step by Step

Step 1: Choose Your Base Background

Do not use pure black (#000000). Pure black has two problems: it creates excessive contrast with almost any content color (making text appear to float off the surface rather than sit on it), and it maximizes the eye strain difference when users switch between tabs or switch windows.

A dark hue at 8-12% lightness in HSL, with a slight tint toward your brand color, is the sweet spot. For a blue-brand product: hsl(222 47% 9%). For a purple-brand product: hsl(260 30% 9%). For a product with no strong brand color, a very slightly warm dark works universally: hsl(220 13% 9%).

Step 2: Define Your Full Surface Scale

Work upward from the base in consistent increments:

[data-theme="dark"] {
  /* Surface scale — each step is 4% lightness increase */
  --surface-100: hsl(222 47% 8%);   /* page background */
  --surface-200: hsl(222 47% 12%);  /* card background */
  --surface-300: hsl(222 47% 16%);  /* modal / elevated */
  --surface-400: hsl(222 47% 20%);  /* tooltip / highest elevation */
  --surface-inset: hsl(222 47% 5%); /* inputs, wells */
}

Step 3: Remap Your Text Colors

Dark mode text is not just white-on-dark. A full text scale needs:

  • Primary text: Not pure white. hsl(210 40% 96%) — a warm off-white that is easier on the eyes at high contrast (typically 15:1+ against the base background)
  • Secondary text: Used for labels, captions, metadata — typically 5-7:1 against the base background
  • Tertiary text / placeholder: Subtle informational text — minimum 4.5:1 for compliance
  • Disabled text: Below 3:1 intentionally, but paired with other disabled state indicators (never rely on color alone)
[data-theme="dark"] {
  --text-primary:   hsl(210 40% 96%);  /* ~14:1 on surface-100 */
  --text-secondary: hsl(215 20% 65%);  /* ~6:1 on surface-100 */
  --text-tertiary:  hsl(215 16% 47%);  /* ~4.5:1 on surface-100 */
  --text-disabled:  hsl(215 16% 32%);  /* ~2.5:1 — paired with opacity */
}

Step 4: Adjust Your Brand/Interactive Colors

Your primary interactive color (buttons, links, active states) will almost certainly need a lighter, slightly desaturated version in dark mode to avoid blooming and to maintain the same 4.5:1 minimum contrast against dark surfaces.

Blue-600 (light mode interactive)#2563EB Blue-400 (dark mode interactive)#60A5FA
:root {
  --color-interactive: hsl(221 83% 53%);    /* Blue-600, 5.9:1 on white */
}

[data-theme="dark"] {
  --color-interactive: hsl(217 91% 75%);    /* Blue-300/400, 7.2:1 on surface-100 */
}

The interactive color shifts two steps lighter in the scale. The saturation may also drop slightly. The key test: does it still pass 4.5:1 against your dark background? If yes, commit.

See muted palettes for examples of how desaturated-but-legible color systems maintain brand character without aggression on dark backgrounds.

Step 5: Validate Semantic Colors

Check every semantic color against your darkest and lightest dark surfaces:

// Automated check for all semantic colors in dark mode
const darkSurfaces = {
  base: '#0d1017',
  card: '#161b22',
  modal: '#1c2128',
};

const semanticColors = {
  success: '#3fb950',  // GitHub-style dark mode green
  warning: '#d29922',
  error: '#f85149',
  info: '#58a6ff',
};

for (const [name, color] of Object.entries(semanticColors)) {
  for (const [surface, bg] of Object.entries(darkSurfaces)) {
    const ratio = contrastRatio(color, bg);
    if (ratio < 4.5) {
      console.warn(`${name} on ${surface}: ${ratio.toFixed(2)} — FAIL`);
    }
  }
}

Semantic Token Architecture for Dark Mode

The technical key to maintainable dark mode is semantic tokens — color variables named by role, not value. This means your component CSS never changes when switching themes.

/* Full semantic token system */
:root {
  /* Surfaces */
  --color-bg:          var(--gray-50);
  --color-bg-subtle:   var(--gray-100);
  --color-bg-muted:    var(--gray-200);

  /* Borders */
  --color-border:      var(--gray-200);
  --color-border-muted: var(--gray-100);

  /* Text */
  --color-fg:          var(--gray-900);
  --color-fg-muted:    var(--gray-600);
  --color-fg-subtle:   var(--gray-400);

  /* Interactive */
  --color-accent:      var(--blue-600);
  --color-accent-hover: var(--blue-700);
  --color-accent-fg:   white;  /* Text on accent background */

  /* Semantic */
  --color-success:     var(--green-600);
  --color-warning:     var(--amber-600);
  --color-danger:      var(--red-600);
}

[data-theme="dark"] {
  --color-bg:          hsl(222 47% 8%);
  --color-bg-subtle:   hsl(222 47% 12%);
  --color-bg-muted:    hsl(222 47% 16%);

  --color-border:      hsl(215 20% 22%);
  --color-border-muted: hsl(215 20% 16%);

  --color-fg:          hsl(210 40% 96%);
  --color-fg-muted:    hsl(215 20% 65%);
  --color-fg-subtle:   hsl(215 16% 47%);

  --color-accent:      hsl(217 91% 75%);
  --color-accent-hover: hsl(217 91% 82%);
  --color-accent-fg:   hsl(222 47% 8%);  /* Dark text on light accent */

  --color-success:     hsl(142 76% 56%);
  --color-warning:     hsl(45 93% 58%);
  --color-danger:      hsl(0 84% 65%);
}

Notice --color-accent-fg changes in dark mode. When your accent color is dark in light mode (dark blue text on white), in dark mode the accent becomes light (light blue), so text on it needs to be dark — not white. This detail is what separates polished dark mode from amateurish implementation.

Ready to create your palette?

Generate with AI

Testing Your Dark Mode Palette

The Brightness Ramp Test

Screenshot your UI in dark mode and convert to grayscale. You should see a clear hierarchy: the base background is darkest, cards slightly lighter, interactive elements clearly distinct from their surroundings, text clearly legible. If everything merges into a similar gray value, your surface steps are too close.

The Blooming Test

Look at your most saturated color (usually your brand accent) on the darkest background in a dimly lit room (or reduce your monitor brightness to 30%). Does the color appear to glow or bleed into surrounding pixels? If yes, reduce saturation on that color until the effect disappears.

The Side-by-Side Brand Consistency Test

Open your light mode and dark mode side by side. Your brand should feel recognizably the same product. If dark mode feels like a different design system entirely, your semantic layer is not coherent.

The System Integration Test

Do not just test in your design tool. Test with OS dark mode enabled, in browsers with dark mode preference, on OLED screens where black is truly black, and on non-IPS displays where viewing angle dramatically affects perceived brightness.

Real Dark Mode Systems Worth Studying

Linear: Near-black surface with precisely controlled surface increments. Their dark mode is visually quieter than the light mode — the linear gradients in the sidebar feel more restrained. The purple accent becomes significantly more muted on dark backgrounds.

GitHub: The most copied dark mode in developer tooling. Surface increments of roughly 4% lightness. Brand green is 3-4 steps lighter than its light mode equivalent. Borders are subtle but consistent.

Figma: Takes the darkest possible base (~#1e1e1e), then uses extremely fine surface increments. The near-black palette keeps attention on the design canvas and makes design colors "pop" against the tooling chrome.

Key Takeaways

  • Never invert your light palette — recalculate every color from the surface up
  • Build a surface hierarchy (at least 3 levels: base, raised, overlay) with consistent lightness increments
  • Shift brand/interactive colors 2-3 steps lighter in dark mode to maintain contrast without blooming
  • Reduce saturation on vivid colors in dark mode to prevent the glow effect
  • Use semantic tokens — components should never reference raw color values
  • Validate all semantic colors (success, warning, error) against dark surfaces independently
  • Test on hardware, not just design tools — OLED screens and low-brightness monitors reveal problems that 100% brightness hides

Dark mode is not an afterthought you add at the end of a project. Built correctly from the start with semantic tokens, it is a 2-hour implementation. Built as a retrofit without semantic tokens, it can be a multi-week nightmare. Invest in the architecture.

Related Guides

accessibility

WCAG Color Contrast: The Complete Guide to Accessible Color Palettes

Master WCAG color contrast requirements — understand AA vs AAA, calculate contrast ratios, fix failing pairs, and build accessible color palettes that pass audits from day one.

design

Color Palette Inspiration: 9 Methods to Find Your Next Perfect Palette

Discover where to find genuine color palette inspiration — from nature and film to brand analysis, historical art, and AI generation. Practical methods that produce palettes people actually remember.

On this page

  • Why You Cannot Simply Invert Your Light Palette
  • 1. Perceptual lightness is nonlinear
  • 2. Saturation reads differently on dark surfaces
  • 3. Semantic meanings shift
  • The Surface Hierarchy Model
  • The Three-Layer Stack
  • Why <BrandLink slug="github">GitHub</BrandLink>'s Dark Mode Works
  • Designing Your Dark Palette Step by Step
  • Step 1: Choose Your Base Background
  • Step 2: Define Your Full Surface Scale
  • Step 3: Remap Your Text Colors
  • Step 4: Adjust Your Brand/Interactive Colors
  • Step 5: Validate Semantic Colors
  • Semantic Token Architecture for Dark Mode
  • Testing Your Dark Mode Palette
  • The Brightness Ramp Test
  • The Blooming Test
  • The Side-by-Side Brand Consistency Test
  • The System Integration Test
  • Real Dark Mode Systems Worth Studying
  • Key Takeaways

Products

  • RRevidCreate viral videos with AI
  • OOutrankOutrank your competition
  • SSuperXGrow your X presence
  • PPostSyncerPost everywhere at once
  • FFeatherShip your website fast

Free Tools

  • AI Reddit Humanizer
  • PostLab
  • SEO AI Review
  • ReplyGuys
  • Find Startup Ideas
  • Letter Ranks
  • Validate SaaS Idea
  • SocialBioGen

 

  • Outrank Playbooks
  • Link Building Playbooks
  • SEO Toolbox
  • Content Gap Analyzer
  • Fake Tweet Maker
  • Color Palette Generator
  • ImagText
  • Story Debrief

TMAKER

Building tools for creators and developers. Ship faster, grow smarter.