Designing logos that work on dark backgrounds
Why black marks and deep brand colors vanish in dark interfaces, which fixes work, and how to test a whole logo set against your dark surface.
6 min read
Most logos are designed on white. Brand teams draw them for light web pages, documents and app store listings, and that is where they check them. Then the logo lands in a wallet or exchange with a near-black interface, and a black wordmark or a navy coin turns into a dark smudge on a dark panel.
You can't redraw other people's logos, but you decide how they are presented. This guide covers how logos fail on dark surfaces, the fixes and their costs, and how to check hundreds of logos at once.
Three ways a logo fails on dark
Black and near-black marks. A logo drawn in #000000 on a dark surface such as #15181E has a contrast ratio of about 1.18:1. In #111111 it drops to about 1.06:1, because that gray is almost exactly as dark as the surface. Either way the shape is gone.
Deep brand colors. Navy, indigo, deep purple and forest green look strong on white and like slightly different blacks on dark gray. A navy such as #1A237E scores about 1.34:1 against #15181E. A deep purple such as #5B21B6 reaches about 2:1: visible, but weak, and it loses detail at small sizes. Bright oranges, yellows and light blues usually survive, landing well above 3:1.
White parts that were never meant to be seen. Some logos draw their inner shapes as white fills instead of real holes. On a white page you can't tell. On a dark page the "holes" show up as white patches, and a coin with a cut-out symbol becomes a coin with a white sticker on it.
A milder fourth problem is thin dark detail inside a bright logo, such as a fine outline or small dark lettering. The logo stays recognizable, but the detail disappears at 16 to 24 px.
How much contrast does a logo need
WCAG sets no contrast requirement for logos. 1.4.11 Non-text Contrast asks for 3:1 for UI components and for graphics needed to understand the content, and logos are explicitly not required to meet it; 1.4.3 Contrast (Minimum) exempts logotypes too. But a token logo exists to identify the token, and one nobody can see fails at that. As a rule of thumb, not a standard:
| Contrast with the surface | What you see |
|---|---|
| Below about 1.5:1 | The shape effectively disappears |
| About 1.5:1 to 3:1 | Visible at large sizes, muddy at 16 to 24 px |
| 3:1 and above | Reads clearly |
Measure every color in the logo, not only the dominant one. If you want the math behind these numbers, see color contrast ratio explained.
Four fixes, and what each costs
| Fix | Works best for | Cost |
|---|---|---|
| Mono version in a light color | Single-color logos, toolbars, dense tables | Drops the brand colors |
| Light tile behind the logo | Multicolor logos with dark parts | A bright shape that draws the eye |
| Faint ring on the container | Dark logos that fill a circle | Weak on free-standing glyphs |
| The brand's own dark-background version | Any brand that publishes one | Only exists for some brands |
The last option is the simplest: when a project publishes a logo for dark backgrounds, it beats anything you derive, so check the brand guidelines or press kit first. The other three need more care.
Mono in a light color
A single-color version in white or light gray is the cleanest answer in monochrome contexts: a navigation bar, a settings list, a dense table where color is noise anyway. The tradeoff is identity, since a coin people know by its color loses part of what makes it recognizable. Making a mono version covers producing one that still reads. In the icon library you can switch an icon to the mono style, where one exists, and pick its color before downloading. If your mono icons are inline SVG with fill="currentColor", a light gray color such as #e4e8ee on the icon is all the dark theme needs, with less glare than pure white.
Light tiles
For multicolor logos with dark parts, put the logo on a small light tile instead of recoloring it. The artwork stays intact, but a bright square in a dark list looks highlighted, so tile only the logos that need it. When a dark logo needs a light tile covers detection and styling.
Rings
A faint light ring on the icon's container gives a dark coin an edge without touching its colors:
.token-icon {
border-radius: 50%;
box-shadow: 0 0 0 1px rgb(255 255 255 / 0.16);
}
This works when the logo fills its circle, as most coin logos do, because the ring traces the coin's own edge. It does little for a free-standing black glyph, where it outlines empty space. Keep the ring on your container rather than adding strokes to the logo: the container is your UI, the paths are someone else's mark.
Testing a whole set
With hundreds of logos you need a first pass that finds suspects for you. This Node script collects the hex colors in each SVG's fill, stroke and stop-color attributes and lists files where every color falls below a threshold against your surface. It also lists files with no explicit color, because a shape with no fill renders black.
// node find-dark-logos.mjs ./svgs
import { readdirSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
const SURFACE = '#15181e'
const THRESHOLD = 1.5
function luminance(hex) {
let h = hex.slice(1)
if (h.length === 3) h = [...h].map((c) => c + c).join('')
const n = parseInt(h, 16)
const [r, g, b] = [n >> 16, n >> 8, n].map((v) => {
const c = (v & 255) / 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)
}
const dir = process.argv[2]
for (const file of readdirSync(dir).filter((f) => f.endsWith('.svg'))) {
const svg = readFileSync(join(dir, file), 'utf8')
const pattern = /(?:fill|stroke|stop-color)="(#(?:[0-9a-f]{3}){1,2})"/gi
const colors = [...new Set([...svg.matchAll(pattern)].map((m) => m[1]))]
if (!colors.length) console.log(`${file}: no hex colors, check for default black`)
else if (colors.every((c) => contrast(c, SURFACE) < THRESHOLD)) console.log(`${file}: ${colors.join(' ')}`)
}
Treat the output as a list of suspects. The script ignores colors in style attributes and <style> blocks, rgb() and named colors, and opacity. For the white-parts problem, search the files for #fff and #ffffff fills and look at each match.
Then do the visual pass: a plain page that renders every logo at 24 px and 48 px on your real surface color. Anything you have to squint at goes on the list, whatever the script said.
Check every surface, not just the page
List rows have hover and selected states, modals often use a lighter panel, and dropdowns may use another gray again. A hover color like #1C2027 is close to a #15181E surface (about 1.09:1 apart), so results barely change. A selected state in a noticeably lighter gray moves closer to mid-tone logo colors and can push a deep purple from weak to gone. Run the script once for each surface color your logos appear on.
If your product also has a light theme, run it there too. Black logos are fine on white, but white-filled parts vanish and pale yellows drop below 2:1. The fixes are the same, mirrored: a dark tile or a darker ring instead of a light one.