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.
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
darklogo is drawn in dark ink. It goes on a light background. - a
lightlogo is drawn in light ink. It goes on a dark background. nullmeans 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:
- Compute the color's contrast against white, using the WCAG contrast formula.
- 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).
- 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
lightvariant in white,#ffffff; - a
darkvariant 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:
<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.