Why images fail in dark mode

A classic example: the company logo, black on a white background, was created for the classic light theme. In dark mode it gets a white padding halo, because many logos have the background color baked into the file. Or a UI icon with dark lines becomes nearly invisible on a dark theme.

Dark mode has established itself in 2026 as a default expectation: 60–70% of users enable it on mobile, 35–45% on desktop (PostHog telemetry 2025). Fail to actively plan images for dark mode and your branding is structurally worse displayed.

Technique 1: prefers-color-scheme in the picture tag

The most robust solution — and supported in all mainstream browsers since 2024 — is the media attribute on <source> elements:

<picture>
  <source srcset="logo-dark.svg" media="(prefers-color-scheme: dark)">
  <img src="logo-light.svg" alt="Brand">
</picture>

Strengths: declarative, robust, no JavaScript, works with lazy loading. Evaluated automatically and reactively on theme change in the browser. Weaknesses: maintaining two separate asset files. With many logos this quickly gets laborious.

When: for logos and complex brand marks where the dark variant should look content-wise different (a knockout version with a different symbol, lighter colors instead of inverted, etc.).

Technique 2: SVG with currentColor

For UI icons and simple logos, the most elegant solution is an SVG that inherits its color from CSS:

<!-- SVG: no baked-in fill -->
<svg viewBox="0 0 24 24" fill="currentColor">
  <path d="M12 2L2 22h20L12 2z"/>
</svg>

/* CSS: a single value for both themes */
.icon { color: var(--text); }
:root[data-theme="light"] { --text: #111; }
:root[data-theme="dark"]  { --text: #eee; }

Strengths: a single file, automatic adjustment to theme variables, compatible with the Icon Studio workflow. Weaknesses: only sensible for mono- and bi-chrome icons. Doesn't work with embedded raster images.

When: UI icon sets, mono logos, line logos, anything SVG-based with clean lines. More background in our SVG/PNG/JPG icon post.

300 × 250 — Rectangle
Cookie-Banner ausstehend

Technique 3: CSS filter as an emergency solution

For legacy images without a dark variant, a CSS filter helps:

@media (prefers-color-scheme: dark) {
  .legacy-logo { filter: invert(1) hue-rotate(180deg); }
}

Strengths: immediately available without an asset change. Weaknesses: the result rarely looks good — gradients rotate wrongly, anti-aliasing becomes visible. When: only as a temporary fallback, not as the default solution.

Technique 4: photos with an adjustable background

With product photos the problem is different: a product photo on a white background looks like a lamp sticker in dark mode. Three solutions:

  • Transparent background (PNG/WebP alpha). The elegant path — the product stands directly on the theme background. Requires a background-removal pipeline.
  • A photo variant per theme. A photo with a light and a photo with a dark background, selected via <picture> + media. High asset effort, best control.
  • A padding box with a theme color. The photo stays on a white background, but CSS surrounds it with a 20 px box in the theme color. Visually the compromise.

Logo-specific workflow

A modern brand logo is maintained today in two or three variants:

  1. Wordmark positive: dark on light. The classic default variant.
  2. Wordmark negative: light on dark. The dark-mode variant.
  3. Mono variant: shape only, single-color. For watermarks, stamps, embossing.

If you build a brand-asset system, derive all three from the same master file. SVG export from Figma can be automated per theme with layer visibility.

300 × 250 — Rectangle
Cookie-Banner ausstehend

Performance implications

Dark-mode images cost bytes. Three practices save them:

  • SVG vars beat bitmap pairs. One SVG file with currentColor replaces two PNG variants.
  • Format cascade per theme. Within the <picture> you can combine both cascades: Light/AVIF, Light/WebP, Light/JPG, Dark/AVIF, Dark/WebP, Dark/JPG. Extends the tag complexity but keeps bytes minimal.
  • Preload only the relevant theme. With server-side theme detection (cookie, User-Agent client hints), preload the matching image directly — saves an unnecessary request.

More on LCP tuning in our Core Web Vitals post.

Theme reactivity without a flash

A subtle trap: if the theme is only set via JavaScript after the first render (the classic FOUC problem), you briefly see the light-theme image on page load, then it switches to the dark-mode image. Solution: theme detection in the HTML server render (the User-Agent client hint "Sec-CH-Prefers-Color-Scheme" has been available in Chromium since 2024) or a synchronous inline-script hack before the body render.

Recommendation matrix

  • UI icons: SVG with currentColor. One file, both themes.
  • Logos: picture tag with two SVG variants (light/dark).
  • Hero images with a photo: picture tag with two photo variants.
  • Product photos: transparent background (PNG/WebP alpha), one file is enough.
  • Legacy logos without a dark variant: short-term a CSS filter, long-term a redesign.
300 × 250 — Rectangle
Cookie-Banner ausstehend

Sources

MDN — prefers-color-scheme · MDN — picture, Art Direction · web.dev — prefers-color-scheme · MDN — Client Hints (Sec-CH-Prefers-Color-Scheme) · W3C SVG 2 — Specifying Paint · MDN — CSS filter · PostHog — Dark Mode Adoption.