web3icons

Supporting dark mode with crypto logos

Keep crypto logos readable in light and dark themes with per-theme files, mono versions and light tiles, plus the CSS and a way to test both.

5 min read

Adding a dark theme to a wallet or dashboard is mostly a matter of swapping color tokens. Text, borders and interface icons follow along. Brand logos don't: they keep their own colors, and some of them were designed for white pages. A black wordmark, a navy coin or a dark gray chain mark can all but vanish on a dark surface, while a white or pale yellow mark can disappear on a light one.

There are three main ways to handle this, and most products end up combining them. First, though, find out which logos actually have a problem.

Find the logos that need help

Sort your logos into three groups:

  • Fine on both. Most colorful marks with mid-tone colors read well on white and on near-black. This is usually the large majority.
  • Too dark for dark mode. Black, near-black, navy and dark gray marks, or logos whose only light parts are small details.
  • Too light for light mode. White marks, pale pastels and some yellows.

With a few dozen logos, you can sort them by eye. With hundreds, compute it. If you have each logo's colors (icon pages on Web3 Icons list them, for example), compare them with your background using the WCAG contrast formula:

// Expects colors as #rrggbb
function luminance(hex) {
  const n = parseInt(hex.slice(1), 16)
  const [r, g, b] = [n >> 16, (n >> 8) & 255, n & 255].map((v) => {
    const c = v / 255
    return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4
  })
  return 0.2126 * r + 0.7152 * g + 0.0722 * b
}

function contrast(a, b) {
  const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x)
  return (hi + 0.05) / (lo + 0.05)
}

// Flags logos whose colors all sit close to the background
export function blendsIn(logoColors, background, threshold = 1.5) {
  return logoColors.every((color) => contrast(color, background) < threshold)
}

Treat the threshold as a starting point, not a rule. WCAG's 3:1 requirement for graphics (1.4.11 Non-text Contrast) doesn't apply to logos, and plenty of fine logos have a dark part or two. Run the check, then review the flagged logos by eye. The contrast ratio guide explains the formula.

Strategy 1: a separate file per theme

Some brands publish a version of their logo for dark backgrounds in their press kit, often with light text or an inverted mark. Where an official version exists, it is the most faithful option.

If your theme follows the operating system setting, HTML can choose the file:

<picture>
  <source srcset="/logos/exchange-on-dark.svg" media="(prefers-color-scheme: dark)" />
  <img src="/logos/exchange-on-light.svg" width="24" height="24" alt="Exchange name" />
</picture>

This follows the system preference, not an in-app theme switch. If your app has its own toggle, choose the file in your code from the current theme, or render both and hide one with CSS:

.logo-on-dark { display: none; }
:root[data-theme="dark"] .logo-on-light { display: none; }
:root[data-theme="dark"] .logo-on-dark { display: inline-block; }

Hiding an <img> with CSS doesn't normally stop the browser from downloading it, so this doubles the requests. That is fine for a handful of logos and wasteful for a list of 200.

An SVG file can also carry its own @media (prefers-color-scheme: dark) rule in a <style> block, which is how some SVG favicons adapt. Like <picture>, it reacts to the browser or system preference rather than your toggle, and browsers have differed in how they apply it to SVG loaded as an image, so test it where it matters.

Strategy 2: mono versions

A single-color version of each logo sidesteps the problem entirely. With fill="currentColor" in inline SVG, or a CSS mask, the icon takes your text color, which your theme already gets right in both modes. The currentColor guide shows the setup.

The trade-off is recognition. People find assets by their colors, so a balances list in all-white mono loses a useful shortcut. Mono works best in secondary places: navigation, filter chips, settings screens, dense tables and network badges. Keep branded logos where identification matters most.

Strategy 3: light tiles

The third option keeps the branded logo and changes what's behind it. In dark mode, a logo flagged as too dark sits on a small light tile or circle, slightly larger than the mark:

.logo { border-radius: 50%; }

:root[data-theme="dark"] .logo.needs-tile {
  box-sizing: border-box;
  background: #e9eaec;
  padding: 3px;
}

Keep the tile off pure white. A bright white disc in a dark list glares; a light gray is calmer and still gives a dark logo plenty of contrast. Keep the logo's total footprint the same size as its neighbors, so the padding comes out of the mark, not on top of it.

This is the approach Web3 Icons uses: logos too dark for its dark interface are shown on a light tile. A related option is a logo style that already includes a colored background, so the mark carries its own backdrop everywhere. The light tiles guide covers when a tile is better than a dark-mode file.

The CSS behind the themes

A theme setup that respects the system default and also allows a manual override looks like this:

:root {
  color-scheme: light;
  --surface: #ffffff;
  --text: #16181d;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --surface: #121418;
    --text: #e8eaee;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --surface: #121418;
  --text: #e8eaee;
}

color-scheme tells the browser to draw form controls and scrollbars in the matching style. If you use the tile rule from Strategy 3, write it for both the system preference and the data-theme override, or tie it to a single class your theme code sets on the root.

Test both themes

Logo problems hide in the long tail, so check deliberately:

  • Build a contact sheet. One internal page showing every logo you use, at its real size, on your light and dark surfaces side by side. Five minutes of scrolling catches what screenshots of three screens never will.
  • Check every state. Hover, selected and disabled backgrounds are different colors from the default surface, and a logo that clears one may not clear another.
  • Check composite icons. Network badges and token pairs usually have a ring in the surface color around the smaller icon. That ring must follow the theme.
  • Look for halos. PNG logos exported on a white background can carry a faint light fringe that only shows in dark mode.

The dark mode accessibility guide covers the rest of the theme, from text contrast to focus styles.

More in Using crypto logos

All guides