Skip to content
All posts

How we pick the right logo variant for dark mode

Why company logos vanish on dark backgrounds, how we classify a logo's ink, when we generate the missing variant, and how to choose one in your UI.

· 6 min read· Brand Identity API

Switch your app to dark mode and look at a list of company logos. A good share of them are missing: not broken, just invisible. A black wordmark on a near-black row has a contrast ratio close to 1:1, so it is there, and nobody can see it.

The fix is to show a different variant of the logo on dark backgrounds. The hard part is knowing which variant you have, and what to do when the brand only ever published one.

Theme means the ink, not the background

Brand APIs, ours included, describe each logo with a theme. It is easy to read backwards, so here is the rule we follow (and Brandfetch's schema uses the same words):

  • a dark logo is drawn in dark ink. It goes on a light background.
  • a light logo is drawn in light ink. It goes on a dark background.
  • null means the artwork brings its own background, like an app icon on a colored square. It works on both.

Because this trips people up, our logo CDN also takes background, which says the same thing from your side: background=dark asks for the logo that reads on a dark page, which is the light one.

How we decide a logo's theme

Most files do not say which background they were made for, so we measure it. After decoding the image (an SVG is sanitized and rasterized first), we reduce its pixels to at most eight colors, each with its share of the visible artwork. Then every color votes:

  1. Compute the color's contrast against white, using the WCAG contrast formula.
  2. Compare it with a threshold: 2:1 for greys and other muted colors, 1.5:1 for saturated ones (a chroma of 30 or more in CIELAB).
  3. A color that passes adds its share to the "reads on white" total; one that does not adds its share to the "reads on black" total.

If the "reads on white" total is at least as large, the logo is dark; otherwise it is light.

We only measure against white because brand inks are designed for white pages. Black, navy and dark grey clear 2:1 easily. The lower bar for saturated colors is there for bright inks such as Spotify's green, #1ed760, which reaches only about 1.9:1 against white: it is drawn for white pages, and we do not want it read as light ink. What fails is the ink that vanishes on white: white itself, pale grey, light yellow.

Because each color votes with its share of the pixels, the color that covers the most of the artwork decides, which is what your eye does too. A thin black outline around a white wordmark does not turn it into a dark logo.

A logo with no transparency, with four matching opaque corners, or that fills its square with a solid plate gets no theme at all: it carries its own background, so it is null.

When we generate the missing variant

Many brands publish one logo. If it is a single color, the opposite variant is not a design decision; it is the same shape in the other ink. So we make it.

A logo counts as single-color when one color covers at least 94% of its visible pixels, or when the colors that are not a close shade of the main one (a CIELAB color distance of 22 or more; the closer shades are anti-aliased edges and subtle gradients) together cover under 5% of the visible pixels.

When a wordmark or a symbol is single-color and it is the only themed version of its type, we draw the other one:

  • a light variant in white, #ffffff;
  • a dark variant in near-black, #111111.

An SVG stays an SVG: we repaint its fills and strokes and leave masks alone, because a mask works on brightness and repainting it would cut the artwork out. A raster logo keeps its alpha channel and gets its color replaced. Either way the new logo is tagged generated, so you can always tell it apart from one the brand published.

We do not recolor multi-color logos. Turning a red and blue mark white would erase the very colors that make it recognizable. For those, the right answer is a light plate behind the logo, which you can add in your UI.

Picking a variant in your interface

With the full record from the Brand API, choose in order: the logo made for your background, then one that works on any background, then the opposite one on a plate.

/**
 * The logo to draw on `background` ("light" or "dark"), and whether it
 * needs a light plate behind it to stay readable.
 */
export function logoFor(brand, background) {
  const ink = background === "light" ? "dark" : "light";
  const wordmarks = brand.logos.filter((logo) => logo.type === "logo");
  const exact = wordmarks.find((logo) => logo.theme === ink);
  const neutral = wordmarks.find((logo) => logo.theme === null);
  const other = wordmarks.find((logo) => logo.theme !== ink && logo.theme !== null);
  const choice = exact ?? neutral ?? other;
  if (!choice) return null;
  const svg = choice.formats.find((format) => format.format === "svg");
  return {
    src: (svg ?? choice.formats[0])?.src,
    generated: choice.tags.includes("generated"),
    needsPlate: choice === other && background === "dark",
  };
}

If you only need an image, the logo CDN does the same choice for you:

html
<picture>
  <source
    media="(prefers-color-scheme: dark)"
    srcset="https://brandidentityapi.com/logo/stripe.com?type=logo&background=dark&size=320"
  />
  <img
    src="https://brandidentityapi.com/logo/stripe.com?type=logo&background=light&size=320"
    alt="Stripe logo"
    width="160"
    height="64"
  />
</picture>

prefers-color-scheme follows the operating system. If your app has its own theme switch that puts a dark class on the page, render both images and let CSS show one:

.logo-on-dark {
  display: none;
}

.dark .logo-on-light {
  display: none;
}

.dark .logo-on-dark {
  display: inline;
}

Check the contrast yourself

The record's colors come with a brightness from 0 to 255, so you can make the same call for text you place on a brand color:

export function textOn(color) {
  return color.brightness >= 150 ? "#111111" : "#ffffff";
}

Why this matters for coverage

Published coverage for light logo variants is low across the industry. Brandfetch, for example, reports a light variant for 58.3% of core brands and 20.4% of long-tail brands in its own coverage figures. Smaller companies often publish a single logo file, and a single-color wordmark is exactly the case generation handles well.

It is still a generated file. If a brand publishes its own light logo later, that one replaces ours the next time the record is refreshed. And if a variant we drew looks wrong, the generated tag lets you filter it out in one line.

The Logo API docs cover theme and background, and the Brand API reference lists every logo field.

Related