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.
Table of Contents
- 1. Why plain color inversion fails on complex illustrations
- 2. Using the dark: variant deliberately for SVG fill and stroke
- 3. Two separate illustration sets versus dynamic CSS variables
- 4. Recoloring illustrations dynamically with CSS custom properties
- 5. Contrast checking for icons specifically in dark mode
- 6. Fallback strategy for icon libraries without built-in dark mode support
- 7. Performance considerations with two parallel illustration sets
- 8. Common mistakes with icons and illustrations in dark mode
- 9. Conclusion: pick the right strategy based on icon complexity
- 10. Summary
- 11. FAQ
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.