Adapting Icons and Illustrations for Dark Mode the Right Way with Tailwind
AI generated
tw
Tailwind CSS · Dark Mode · SVG · Illustration
Icons and Illustrations
adapted for dark mode properly instead of just inverted

Building dark mode support for text and surfaces with Tailwind's dark: variant is usually quick, but icons and especially complex illustrations pose their own problem. A simple CSS filter inversion turns a multi-colored illustration into an unreadable mess of colors, because inversion flips every color independently of its original meaning. Working deliberately with dark: variants at the SVG level instead keeps full control over fill, stroke and contrast in both modes.

14 min read SVG · fill/stroke · dark: WCAG 1.4.11 non-text contrast

1. Why plain color inversion fails on complex illustrations

The fastest apparent path to a dark-mode-ready illustration is a CSS filter such as filter: invert(1) applied to the whole SVG or image. For a simple single-color icon that might still work, since black turns to white and vice versa. As soon as an illustration contains multiple colors though, say a blue element, an orange accent detail and a beige background, the inversion flips each of those colors independently and produces combinations that no longer relate to the original image's intent at all.

The problem becomes especially visible with illustrations that include skin tones, brand colors or subtle gradients: an inverted beige suddenly looks blue-gray, an inverted brand color no longer matches the tone from the style guide, and gradients can develop unnatural edges. Adding a hue-rotate on top of the inversion often only patches part of the problem and introduces new distortions for other colors in the same image, because a single global filter can never correctly treat every color in the image on its own.

2. Using the dark: variant deliberately for SVG fill and stroke

For simple icons embedded as inline SVG in the markup, Tailwind's dark: variant is the most direct path. Instead of filtering the whole image, the color of individual paths is controlled directly through the fill and stroke attributes, by having the SVG use fill="currentColor" and adding a dark: variant to the text color of the wrapping element. That way the icon inherits a dark color in light mode and automatically a light color in dark mode, with no filter of its own.

For multi-color icons where not every path should share the same color, individual path or g elements can be given Tailwind classes directly, for example class="fill-blue-600 dark:fill-blue-400" on one path and class="fill-gray-800 dark:fill-gray-200" on another. This granular control takes more effort than a global filter, but produces a result that genuinely looks like the same illustration in both modes, just in adapted tones.


<!-- Inline SVG with currentColor: inherits text color including dark: -->
<span class="text-gray-700 dark:text-gray-300">
  <svg viewBox="0 0 24 24" fill="currentColor" class="h-6 w-6">
    <path d="M12 2 2 7l10 5 10-5-10-5Z" />
  </svg>
</span>

<!-- Multi-color icon with targeted fill classes per path -->
<svg viewBox="0 0 48 48" class="h-10 w-10">
  <path d="M4 24a20 20 0 1 1 40 0" class="fill-sky-600 dark:fill-sky-400" />
  <circle cx="24" cy="24" r="6" class="fill-amber-500 dark:fill-amber-300" />
</svg>

3. Two separate illustration sets versus dynamic CSS variables

For elaborate illustrations with many color areas, gradients and fine detail, per-path dark: class assignment quickly hits practical limits, since a single SVG can contain dozens of paths. Two basic strategies apply here: either maintain two entirely separate illustration files, one for light mode and a dedicated version designed for dark mode, or build the illustration once with CSS custom properties for every color area and override those variables centrally in dark mode.

Two separate sets deliver the visually best result, because a designer can deliberately adjust contrast, gradients and even composition details in the dark-mode set that would look different in light mode. The downside is doubled maintenance effort on every change. The CSS variables solution is more maintenance-friendly, since only one SVG exists, but it limits creative freedom, because by definition only color values can be swapped, not shapes or composition.

4. Recoloring illustrations dynamically with CSS custom properties

For the variables approach, every relevant color area in the SVG gets its own CSS custom property as its fill color, for example fill: var(--illu-primary) and fill: var(--illu-accent). These variables are defined with light-mode values in the regular stylesheet and then overridden with different values inside the dark: media query or the corresponding Tailwind dark class on the wrapping container. The SVG itself stays completely unchanged; only the context it renders in decides the colors.

This approach combines well with Tailwind v4 by defining the variables inside the @theme block or directly in a utility class with @layer components. The practical benefit shows up most clearly for illustrations that appear multiple times on a page, for example in a feature list with four similar icons: a single central variable change immediately affects every occurrence, whereas with separate SVG files every instance would need to be swapped individually.

5. Contrast checking for icons specifically in dark mode

For plain text, WCAG criterion 1.4.3 applies with a minimum contrast of 4.5 to 1, but for graphical objects like icons and their outlines, criterion 1.4.11, the so-called non-text contrast, applies instead. It requires a contrast ratio of at least 3 to 1 between an icon and its immediate surroundings, provided the icon is necessary to understand the interface, such as a warning symbol or a status indicator. A light gray that offered sufficient contrast in light mode often drops below this threshold on a dark background in dark mode.

In practice it is worth checking every icon base color used in the project against its typical background separately for both modes with a contrast calculator, instead of relying on a gut feeling for sufficient visibility. Medium gray tones are particularly tricky here, since they can look acceptable at first glance in both modes but slip below the 3 to 1 threshold in one of them under precise measurement, requiring targeted adjustment for that mode.

6. Fallback strategy for icon libraries without built-in dark mode support

Many ready-made icon sets shipped as single-color SVG files already support the currentColor pattern by default, since they only use one line color anyway. For sets like this, a text-* or dark:text-* class on the wrapping element is usually enough, without needing to change anything in the SVG file itself. The only real problem arises with icon sets that hard-code fixed hex colors directly in the fill attribute of every single path.

In that case, a one-time cleanup step on import pays off: every hard-coded fill="#000000" attribute is replaced with fill="currentColor" via search and replace or a small build script, before the file is adopted into the project. This one-time effort pays off because every icon in the set then automatically benefits from the central dark: color control, instead of needing manual fixing for every new icon.

7. Performance considerations with two parallel illustration sets

Anyone opting for two separate illustration sets should avoid loading both variants in the markup at once and only toggling visibility via CSS, since that actually transfers twice the image data even though only one variant is visible. It makes more sense to insert only the appropriate SVG into the DOM server-side or via JavaScript based on an already-known color scheme preference, for instance with an Alpine.js x-show combined with x-cloak, so no brief flash of the wrong variant occurs.

For inline SVGs embedded directly in the HTML this problem is smaller, since no extra network request is needed, but the raw DOM size still grows if both sets sit in the markup in parallel. For illustrations that are not above the fold, a simple approach decided server-side via a cookie or prefers-color-scheme is usually the most pragmatic solution, since it avoids unnecessary overhead in the initial render.

8. Common mistakes with icons and illustrations in dark mode

A common mistake is coloring icons with the same gray shade as body text across the board, without considering that icons as graphical objects fall under a different contrast rule than text. A second widespread mistake is using filter: invert() on entire illustrations, which, as described earlier, almost always causes unwanted color shifts on multi-color images and often looks worse in the end than no dark mode adaptation at all.

A third mistake concerns transparent PNG or WebP illustrations with dark outline strokes that work fine on a light background in light mode but suddenly become nearly invisible in dark mode, since dark lines on a dark background offer barely any contrast. Unlike SVGs, such raster images cannot be recolored via CSS, which is why they should almost always be replaced with SVG versions once a project gets real dark mode support.

9. Conclusion: pick the right strategy based on icon complexity

For simple, single-color icons, currentColor combined with Tailwind's dark:text-* classes is almost always the right and cheapest path, since it needs no extra files or variables. For multi-color icons with a handful of color areas, targeted per-path fill class assignment directly in the SVG is worthwhile, while for complex illustrations either two maintained sets or a CSS variables solution is the better choice depending on team resources and desired creative freedom.

In every case, contrast checking against WCAG 1.4.11 should happen separately for both modes, because sufficient contrast in light mode is by no means guaranteed to persist in dark mode. Anyone who makes this check a fixed step in the review process for new icons and illustrations avoids the most common dark mode regressions before they ever go live.

Strategy Effort Design freedom Suited for
currentColor + dark:text-* Very low Low, single color only Single-color icons
Per-path fill classes Medium Medium, several color areas Multi-color icons with few paths
CSS custom properties Medium to high Color values only Recurring complex illustrations
Two separate SVG sets High, doubled maintenance Maximum, including shape/composition Hero illustrations, marketing pages

Mironsoft

Tailwind CSS architecture, design systems, and performance

Tailwind frontends that stay maintainable despite thousands of utility classes?

We review existing Tailwind projects for bloated class lists, inconsistent design tokens, and unused CSS remnants, then build a design system that scales cleanly instead of getting messier with every component.

Design System Review

Checking tokens, spacing scale, and component consistency for maintainability.

Performance Optimization

Systematically reducing CSS bundle size, purge configuration, and load times.

Component Architecture

Building reusable, well-structured components instead of sprawling class lists.

10. Summary

Icons and Illustrations in Dark Mode: The Essentials at a Glance

Problem with inversion

filter: invert() flips every color independently and destroys the original visual intent of multi-color illustrations.

Simple icons

currentColor in the SVG combined with Tailwind's dark:text-* classes solves most single-color icon cases with no extra effort.

Complex illustrations

Either two maintained SVG sets for maximum design freedom, or CSS custom properties for low-maintenance reuse.

Contrast rule

WCAG 1.4.11 requires 3 to 1 for meaningful icons, checked separately for light and dark mode.

11. FAQ: Icons and Illustrations in Dark Mode: The Essentials at a Glance

1Why does filter: invert() often look bad on illustrations in dark mode?
Because inversion flips every color in the image independently of its original meaning. On multi-color illustrations this produces color combinations that no longer relate to the original design, such as distorted brand colors or unnatural-looking skin tones.
2How do you automatically color a single-color SVG icon for dark mode?
The SVG gets fill="currentColor", and the wrapping text color is controlled with a Tailwind class like text-gray-700 dark:text-gray-300. The icon then automatically inherits whichever text color is active, with no CSS filter of its own.
3How do you handle multi-color icons with only a few paths?
Every relevant path or group in the SVG gets its own Tailwind class with a fill and dark:fill value, for example fill-sky-600 dark:fill-sky-400 on one path and fill-amber-500 dark:fill-amber-300 on another.
4When is it worth maintaining two completely separate illustration sets?
For elaborate illustrations with many color areas, gradients or composition details that should be deliberately different in dark mode. The benefit is maximum design freedom, the downside is doubled maintenance effort on every change.
5How does the CSS custom properties solution for illustrations work?
Every color area in the SVG references a CSS variable as its fill value, such as fill: var(--illu-primary). The variable is defined normally for light mode and overridden with a different value in the dark mode context, while the SVG itself stays unchanged.
6Which WCAG contrast value applies to icons instead of text?
Criterion 1.4.11, the non-text contrast, requires at least 3 to 1 between a meaningful icon and its immediate surroundings, while body text under criterion 1.4.3 needs at least 4.5 to 1.
7Why is sufficient icon contrast in light mode often not enough in dark mode?
Because the background changes fundamentally, and a medium gray tone that offers sufficient contrast on white often falls below the 3 to 1 threshold on a dark background. Both modes therefore need to be measured separately.
8How do you handle ready-made icon libraries with hard-coded hex colors?
On import, every fixed fill="#000000" attribute is replaced with fill="currentColor" via search and replace or a build script. After that, every icon automatically benefits from the central dark: color control through the wrapping text color.
9What should you watch out for with transparent raster illustrations like PNG in dark mode?
Dark outline strokes that work fine in light mode become nearly invisible on a dark background. Since raster images, unlike SVGs, cannot be recolored via CSS, they should be replaced with SVG versions for genuine dark mode support.
10How do you avoid performance problems with two parallel illustration sets?
Instead of loading both variants and only hiding one via CSS, only the appropriate SVG should be inserted into the DOM server-side or via JavaScript based on the known color scheme preference, for instance with Alpine.js x-show combined with x-cloak.