Paid Media SEO / GEO AI Automation Web Design About Blog GET AUDIT →
Web Design

Dark Mode Website Without Breaking Your Design System

9 min read 27 August 2026 By Amrit · Workflow AI Advisors
Dark Mode Design Systems Web Design CSS UI/UX

Dark mode is one of those features that looks simple from the outside and turns into a structural problem the moment you start building it. Most teams make the same mistake: they treat dark mode as a colour inversion — flip the backgrounds, lighten the text, call it done. Three weeks later they're firefighting broken contrast ratios, illegible data visualisations, washed-out brand colours, and a component library that behaves differently depending on which mode a user triggers.

The real issue isn't dark mode itself. It's that most design systems weren't architected to support two visual contexts simultaneously. When you bolt dark mode on top of a system that wasn't built for it, the seams show. This post walks through how to build dark mode properly — from token architecture to component testing — so it strengthens your design system rather than undermining it.

Why Dark Mode Breaks Most Design Systems

Before getting into solutions, it's worth being precise about why this problem is so common. Design systems typically store colours as static values — a hex code assigned to a component once, at build time. The button is #1A73E8. The background is #FFFFFF. The text is #202124. These values work perfectly in one context, and that's exactly the problem.

When you introduce dark mode, you're not just changing colours — you're introducing a second semantic layer. The same interface element now needs to communicate the same meaning across two completely different chromatic environments. A primary action button can't just be "blue." It needs to be the right blue for light contexts and a different (or adjusted) blue for dark contexts, while still being recognisably "primary action" to the user.

Shadows are another common casualty. Drop shadows that create depth on white backgrounds become invisible on dark surfaces — or worse, they create a garish glow effect. Elevation and depth signals need to be rethought, not just recoloured.

Start With Design Tokens, Not CSS Variables

The foundation of a dark-mode-ready design system is a proper token architecture. If you're not using design tokens yet, dark mode is the forcing function to implement them correctly.

Tokens operate in three tiers:

  • Global tokens — your raw palette. Every colour your brand uses, named literally: blue-500, neutral-900, red-300. These never change between modes.
  • Semantic tokens — the meaningful assignments. color-background-primary, color-text-default, color-border-interactive. These tokens reference global tokens and are what your components actually consume.
  • Component tokens — optional but powerful. button-background-hover, card-surface-elevated. These reference semantic tokens and give you surgical control when components need to diverge from the system defaults.

Dark mode works by swapping the semantic layer. Your light theme maps color-background-primary to neutral-0 (white). Your dark theme maps the same token to neutral-950 (near-black). Every component that uses color-background-primary switches automatically. No component-level overrides. No duplicate CSS. No maintenance debt.

In practice, this typically looks like:

:root {
  --color-background-primary: #FFFFFF;
  --color-text-default: #1C1C1E;
  --color-border-subtle: #E0E0E0;
}

[data-theme="dark"] {
  --color-background-primary: #0F0F0F;
  --color-text-default: #F2F2F7;
  --color-border-subtle: #3A3A3C;
}

Your components reference var(--color-background-primary) and the rest happens automatically when the attribute flips. This approach also integrates cleanly with prefers-color-scheme for system-level detection, while still giving users manual override capability — which is the right UX pattern.

Contrast Is a Science, Not a Guess

WCAG AA requires a minimum contrast ratio of 4.5:1 for normal text and 3:1 for large text. These ratios are harder to achieve in dark mode than most designers expect, particularly with mid-range colours.

Brand colours are the usual problem. A primary blue that passes contrast against white often fails against dark backgrounds at the same lightness level. You can't simply use the same colour in both contexts and expect accessibility compliance. For each semantic role — primary, secondary, destructive, success — you need to audit and potentially define separate dark-mode values.

Tools worth integrating into your workflow:

  • Figma's built-in contrast checker — run this on every text-on-background pairing in your dark components
  • Colour Contrast Analyser by TPGi — desktop tool, useful for testing beyond Figma
  • Stark — Figma plugin with vision simulation, so you can see how dark mode renders for users with colour vision deficiencies

One practical tip: build a dedicated "contrast audit" page in Storybook or your component playground that renders all text-on-surface combinations in both modes side by side. This makes regression testing fast and makes it impossible for dark-mode colour changes to ship without a visual sign-off.

Elevation and Depth in Dark Mode

Material Design's dark mode guidance introduced an elegant solution to the elevation problem that's worth understanding even if you're not using Material: use lightness to communicate elevation in dark mode, rather than shadows.

In light mode, cards and modals sit above a white background, so shadows are the natural depth signal. In dark mode, you often have very little room between surface colours and true black — so shadows read as almost nothing. The alternative is to progressively lighten surface colours as components sit higher in the visual stack.

A practical implementation:

  • Base background: #0F0F0F
  • Elevated card: #1C1C1E
  • Modal / overlay surface: #2C2C2E
  • Tooltip / highest layer: #3A3A3C

Each step up in elevation gets a slightly lighter surface. Users read this correctly as depth without you needing shadows at all — and it's far more accessible for users in dark environments with reduced screen brightness.

Images, Icons, and Media

Vector icons with hardcoded fill colours break in dark mode if they're set to dark values (e.g. #1C1C1E) for light mode use — they'll become invisible on dark surfaces. The fix is ensuring all icons use currentColor as their fill, so they inherit from the text colour of their context automatically.

Raster images are more complex. Photographic images generally require no changes — they look fine in both modes. Illustrative images with white or light backgrounds create a jarring visual box against dark surfaces. Options include:

  • Using SVG illustrations that respect token-based fills
  • Applying mix-blend-mode: screen or multiply to images contextually
  • Providing separate dark-mode image variants for key illustrations
  • Applying a subtle CSS filter to reduce harsh image brightness: filter: brightness(0.85)

There's no universal answer here — it depends on your illustration style and how heavily image-driven your UI is. The important thing is to audit this explicitly rather than discovering broken visuals post-launch.

Component-by-Component Testing Strategy

When dark mode is structural (built with tokens, not overrides), most components adapt automatically. But a subset of components will always need manual review. Prioritise your audit in this order:

  1. Form elements — inputs, selects, and checkboxes often have browser-default styles that don't respect custom tokens in dark mode without explicit overrides
  2. Data visualisations — charts, graphs, and tables need a full colour audit; axis lines and grid lines that are barely visible in light mode often disappear entirely
  3. Modals and drawers — elevation signals and overlay backgrounds need dark-specific values
  4. Notification and badge components — status colours (red for error, green for success) often need lightness adjustment to maintain readability
  5. Third-party embeds — maps, video players, analytics widgets — these frequently don't respect your theme at all and need containment strategies

The User Experience of Switching

How you implement the mode toggle matters more than most teams appreciate. The transition should feel intentional and controlled:

  • Apply a CSS transition (transition: background-color 200ms ease, color 200ms ease) on the root element to prevent jarring flashes
  • Persist user preference in localStorage and respect prefers-color-scheme as the default before any explicit user choice is stored
  • Avoid applying the transition on page load — this causes a flash of unstyled content as the preference is read. Apply the transition class only after the initial render
  • Include the mode preference in your analytics events so you can understand what percentage of your users actually use dark mode — this data is often surprising and informs prioritisation decisions

Building Dark Mode Into Your Design Process From the Start

The teams that handle dark mode best treat it as a first-class design requirement, not a post-launch feature request. In practice, this means:

  • Maintaining parallel Figma frames in both modes for every screen and component in your library
  • Running contrast checks in both modes as part of design review, not QA
  • Including dark mode in your definition of "done" for any new component
  • Automating token export so that Figma design tokens and production CSS variables stay in sync (tools like Token Studio or Style Dictionary make this achievable)

At Workflow AI Advisors, our web design and build process treats token architecture and dual-mode support as infrastructure decisions made at the project outset — not cosmetic choices made at the end. The cost of retrofitting dark mode into a mature codebase is consistently higher than the cost of building it into the system from day one.

It's also worth noting that dark mode has direct implications for your digital performance. Sites that implement dark mode well — with clean semantic markup, efficient CSS custom property usage, and accessible contrast — tend to perform better on Core Web Vitals. That's a factor that technical SEO and GEO increasingly accounts for, particularly in competitive markets where page experience signals genuinely move rankings.

Common Mistakes to Avoid

To summarise the failure patterns we see most frequently when auditing existing implementations:

  • Inverting colours mathematically — using CSS filter: invert(1) on the whole page. This breaks images, videos, and any element with intentional colour meaning.
  • Hardcoding dark-mode colours in component CSS — bypassing the token layer and creating a maintenance nightmare at scale
  • Not testing at reduced brightness — dark mode is most commonly used in low-light environments. Test on a device with screen brightness at 30-40% before signing off
  • Forgetting print stylesheets — if your site has printable content, dark mode backgrounds need to be explicitly stripped for print media queries
  • Skipping user testing — developer and designer preferences for dark mode aesthetics frequently don't match actual user behaviour. Test with real users across age groups and devices

Dark mode done right is a signal of design system maturity. It requires you to be precise about semantics, rigorous about accessibility, and disciplined about architecture. The teams that get it right aren't working harder — they're working with better structural foundations.

Frequently Asked Questions About Dark Mode Website Design Systems

What is the best way to implement dark mode in a design system?

The most robust approach is a three-tier design token architecture: global palette tokens, semantic tokens (which assign meaning to colours), and optional component tokens. Dark mode works by remapping semantic tokens to different global values — so every component that consumes semantic tokens adapts automatically without manual overrides. This approach scales cleanly and keeps maintenance overhead low.

Do I need separate colour values for dark mode, or can I just invert my light mode colours?

You need separate values. Mathematical inversion or colour flipping rarely produces accessible contrast ratios or correct brand representation. Brand colours especially — primary blues, greens, reds — typically need lightness adjustments to meet WCAG contrast requirements on dark surfaces. The correct approach is to define explicit dark-mode mappings for each semantic token, not derive them algorithmically from light-mode values.

How do I handle shadows and elevation in dark mode?

Drop shadows that communicate elevation in light mode become nearly invisible on dark backgrounds. The most effective solution is to use progressively lighter surface colours to signal elevation — each layer higher in the visual stack gets a slightly lighter background value. This approach was popularised by Material Design's dark mode specification and works reliably across different display types and brightness settings.

How should I persist a user's dark mode preference?

Store the user's explicit preference in localStorage, and use the prefers-color-scheme media query as the default before any explicit choice has been made. This gives users system-level automatic behaviour by default, while respecting any override they make manually. Critically, read and apply the stored preference before the first paint to avoid a visible flash of the wrong theme — typically achieved by injecting a small inline script in the document head that sets the theme class synchronously.

Does dark mode affect SEO or Core Web Vitals?

Dark mode itself doesn't directly affect search rankings, but the technical implementation can. A poorly built dark mode — using JavaScript-heavy colour switching, large duplicate CSS files, or layout-triggering transitions —