avicon

Dark Mode Favicons: How to Make One Icon Work on Both Themes

How to build a favicon that adapts to light and dark browser themes using an SVG media query — and why the safer answer is usually an icon that never needs to.

By Abdessamad Bettal · 7 min read

Quick answer: put a @media (prefers-color-scheme: dark) block inside an SVG favicon and browsers that support SVG icons will recolour it when the user's theme changes. Ship it as <link rel="icon" type="image/svg+xml" href="/icon.svg"> alongside a normal PNG and favicon.ico fallback, because not every browser reads SVG favicons. The more robust fix, if you only do one thing, is to design a mark that survives both themes without switching at all.

The problem shows up the moment someone with a dark browser theme opens your site: a black logo on a transparent background becomes a black square on a near-black tab strip. It's still technically there. Nobody can see it. The same happens in reverse to sites that designed for dark and got a light-themed visitor.

There are three ways to deal with this, and they are not equally good.

Option 1: an SVG favicon that recolours itself

This is the only approach where a single file genuinely responds to the theme. An SVG is a document, so it can carry its own stylesheet — including media queries that the browser evaluates when it renders the icon.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <style>
    /* Light theme (the default) */
    .bg { fill: none; }
    .fg { fill: #171334; }

    @media (prefers-color-scheme: dark) {
      .fg { fill: #ffffff; }
    }
  </style>
  <rect class="bg" width="32" height="32" rx="7"/>
  <path class="fg" d="M8 24 L16 7 L24 24 Z"/>
</svg>

Declare it the normal way, with fallbacks below it:

<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" type="image/svg+xml" href="/icon.svg">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">

Order matters less than people assume — browsers pick the icon they consider best rather than the first one listed — but keeping favicon.ico declared means anything that can't parse the SVG still has something to fall back to.

Three details trip people up here:

The media query follows the operating system, not your site. If a visitor has their OS in dark mode but is using your site's light theme via a toggle, the favicon will render dark-mode colours. There is no way for the icon to read your site's own theme state, because the browser renders the icon outside the page. If your site's theme toggle matters more than the OS setting, this approach will occasionally look inconsistent, and there's no fixing it.

External CSS, scripts and fonts are ignored. A favicon SVG is rendered in a restricted mode: no external stylesheets, no <script>, no web fonts, no external images. Everything has to be inline, and text should be converted to paths rather than relying on a font being available. An SVG that looks perfect when opened in a browser tab as a document can still render blank as a favicon for exactly this reason.

Browsers cache favicons hard. Chrome in particular holds onto a favicon far longer than a normal asset, so flipping your OS theme may not repaint the tab until you force a reload — or until you restart the browser. When you're testing, assume you're looking at a stale icon until you've proven otherwise.

Option 2: two files and a media attribute

You'll find this pattern in a lot of blog posts:

<link rel="icon" href="/icon-light.png" media="(prefers-color-scheme: light)">
<link rel="icon" href="/icon-dark.png" media="(prefers-color-scheme: dark)">

It reads beautifully and it is the obvious way this should work. Browser support for the media attribute on icon links has been inconsistent for years, though, and it fails in a bad way: a browser that ignores media sees two equally valid icon declarations and picks one, effectively at random from your point of view. You get a coin flip rather than a fallback.

If you use this at all, use it as an enhancement on top of a correct unconditional icon, never as the mechanism you rely on:

<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" type="image/svg+xml" href="/icon.svg">

In other words, prefer option 1. The SVG does the same job inside one file, without the possibility of two competing declarations.

Option 3: design an icon that doesn't care

This is the boring answer and it's the one I'd give to most sites. An icon that reads on both themes needs no media query, no second file, no cache-busting and no browser support table.

There are a few reliable ways to get there:

Give the mark its own background. The single most effective change is to stop shipping a transparent icon. A filled shape — a rounded square, a circle, a squircle — in your brand colour, with the mark knocked out of it in white or near-white, sits on any tab strip because it brings its own contrast. This is exactly what iOS forces you to do for touch icons, and it's why touch icons rarely have this problem.

Avoid pure black and pure white as the only ink. A mark drawn in #000 disappears on dark chrome; #fff disappears on light. A mid-tone with reasonable contrast against both — a saturated brand colour, or a dark grey with a light outline — survives in both directions.

Check the silhouette, not the colours. At 16 pixels the shape is doing nearly all of the recognition work. If your icon is identifiable in a single flat colour against both a white and a near-black background, it's theme-proof.

A quick way to test: open your icon at 16, 32 and 48px side by side on a #ffffff background and on a #202124 background — that second value is close to Chrome's dark tab strip. If one of the pairs vanishes, the design needs to change before the markup does.

Which browsers actually honour it

Support for SVG favicons is good in current Chromium browsers and Firefox, and unreliable in Safari, which has its own separate history with icon formats. Rather than trusting a support table that ages badly — this is an area where the ground has moved several times — treat it as a rule of shipping:

  • Always ship a multi-resolution favicon.ico and PNGs. These are the universal path.
  • Add the SVG as an enhancement for browsers that read it.
  • Never let the SVG be the only icon you declare.

That way the dark-mode behaviour is a bonus for the browsers that support it, and the browsers that don't still get a correct, visible icon.

Don't forget the theme colour

While you're here: dark mode also affects the browser UI colour on mobile, which is a different tag entirely.

<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#171334" media="(prefers-color-scheme: dark)">

Unlike the icon media attribute, media on theme-color is well supported and does the right thing. It won't fix an invisible favicon, but a light theme-color behind a dark-mode favicon is its own kind of jarring.

The same applies to theme_color and background_color in your web app manifest, which control the splash screen when someone installs your site. Those are single values, not media-query-aware, so pick the one that matches your icon's own background rather than your current page theme.

A working checklist

  1. Design the mark with its own background so it works on any chrome. This alone solves the problem for most sites.
  2. Export the standard set: multi-resolution favicon.ico, PNGs at 16/32/180/192/512, and the manifest.
  3. If you want theme-reactive behaviour, hand-write an SVG with an inline @media (prefers-color-scheme: dark) block, with all text converted to paths and no external references.
  4. Declare favicon.ico and the SVG together; keep the PNG touch icon for iOS.
  5. Add theme-color meta tags for both schemes.
  6. Hard-reload — or use a private window — before you believe what you're seeing, then flip your OS theme and check again.

If you want the whole standard set generated for you, the Generator exports every size along with the <head> snippet, and the Checker will tell you which icons a live URL is currently declaring — which is usually how you discover that the theme-aware SVG you shipped last month is being ignored in favour of an old favicon.ico you forgot was there.

Try the Generator

Drop in artwork, type a word, or pick an emoji. Watch it land in a real browser tab, then export every size a modern site needs.

Open Generator

Written by Abdessamad Bettal

Web developer, and the person who builds and writes favicon.tools. The ICO packer, manifest writer and site checker behind favicon.tools were all written from the file-format specs and tested against real browsers — which is where the detail in these guides comes from. Spotted something wrong? Write to contact@favicon.tools.