avicon

How Browsers Actually Decide Which Favicon to Show

Declare five icons and the browser displays one. The selection rules aren't the order you wrote them in — here's what governs the choice, and why two sites with identical markup can show different icons.

By Abdessamad Bettal · 7 min read

Quick answer: browsers don't use the first icon you declare, or the last. They collect every candidate, filter out the ones they can't render, and pick the best size match for the surface they're drawing — then fall back to /favicon.ico if nothing usable was declared. The practical consequences: an unwanted icon usually means a stale declaration you forgot about, and the sizes and type attributes are how you influence the choice.

This is the piece of favicon behaviour most people never learn, and it explains a surprising number of "it's showing the wrong icon" problems. You add a new icon, the old one keeps appearing, and there's nothing obviously wrong with your markup — because there isn't. The browser is making a choice, and it isn't making it the way you assumed.

The selection is a filter, not a list

When a browser parses your page it builds a set of icon candidates from every <link rel="icon">, <link rel="apple-touch-icon">, and manifest entry it finds. Then, for each place it needs an icon — the tab, the bookmark bar, a home-screen shortcut — it goes roughly like this:

  1. Discard what it can't render. A browser without SVG icon support drops the SVG. One that doesn't parse ICO drops the ICO. This filtering happens before anything else, which is why fallbacks work at all.
  2. Discard what fails to load. A 404, or a file served with a content type that doesn't match its contents, removes that candidate. This is silent — no console error for a failed favicon in most browsers.
  3. Rank what's left by fit. The browser knows it needs, say, 32 device pixels. It prefers an exact match, then the nearest larger option to downscale from, then a smaller one to scale up. A resolution-independent SVG is generally treated as an ideal fit for any size.
  4. Break remaining ties by its own preferences. Given two equally good candidates, browsers apply their own ordering — several prefer PNG over ICO, and some prefer the later declaration. This part is not standardised, and it's where two browsers legitimately disagree.
  5. Fall back to /favicon.ico if there were no usable candidates at all. This request happens whether or not you declared it.

Nothing in that sequence is "use the first tag." Document order only matters as a tiebreaker, in some browsers, at the very end.

Why this causes real problems

The old icon that won't die

You add favicon-32x32.png and it works locally. In production, the old logo is still there. What's usually happening: an old favicon.ico is still sitting at your site root. Your new PNG is declared, but the browser had already cached an icon for that origin, and the cached entry has a long lifetime.

The tell is that a private window shows the new icon while your normal window doesn't. If both show the old one, you have a second declaration somewhere — check the served HTML, not your template.

The SVG that hijacked everything

Framework starters ship an SVG icon and a matching <link> tag: vite.svg, favicon.svg, the Next.js and Astro equivalents. Because an SVG is a perfect fit at every size, browsers that support SVG icons will rank it above your carefully-exported 32×32 PNG — every time.

Your icons aren't broken and your paths aren't wrong. The starter's icon is simply winning on merit. Delete the tag and the file.

Two tags, two browsers, two answers

If you declare two icons of equivalent quality and let the tiebreaker decide, Chrome and Firefox may make different choices. This is legal behaviour. The fix isn't to reorder them, it's to not create the tie: declare one icon per format and size, and let the filtering steps do their job.

Using sizes and type properly

These two attributes are how you feed the ranking step, and most snippets use them carelessly.

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

type lets a browser rule out a format it can't render without downloading it first. Getting it wrong is worse than omitting it — a PNG declared as image/x-icon may be discarded by a browser that trusts the declaration.

sizes describes what's in the file. It's a hint, not an instruction: it doesn't resize anything, and lying about it just makes the browser rank the file badly. For a multi-resolution .ico containing 16, 32 and 48, the honest value is sizes="16x16 32x32 48x48", though sizes="32x32" is the common shorthand and works fine in practice.

sizes="any" is the correct value for an SVG, and it's a genuinely useful signal — it tells the browser this candidate fits every requirement:

<link rel="icon" type="image/svg+xml" href="/icon.svg" sizes="any">

There's a well-known variant of this used to demote an ICO in favour of an SVG, by declaring the ICO with a specific size and the SVG with any. It works, but it relies on ranking behaviour rather than a guarantee. Treat it as a nudge, not a switch.

Where the manifest fits in

Icons declared in a web app manifest are a separate pool from <link rel="icon"> tags, used for a different set of surfaces: install prompts, home-screen shortcuts, splash screens, app switchers. Browsers do not generally use manifest icons for the tab, and they do not use your tab favicon for an installed app icon.

That separation is why a site can have a perfect tab icon and a blank install prompt, or vice versa. They're answering different questions and read different declarations. Same for apple-touch-icon, which is its own third pool that only iOS consults.

Three pools, three surfaces:

Surface Reads
Browser tab, bookmarks, history <link rel="icon">, then /favicon.ico
iOS home screen <link rel="apple-touch-icon">, then a screenshot
Android / PWA install manifest icons, then sometimes the tab icon

Google is not a browser

Worth stating, because it catches people out: Google's crawler runs its own selection process for the icon it shows next to a search result, with its own requirements — a stable URL, a crawlable location, a size it considers acceptable, and its own caching schedule that operates on the order of weeks, not minutes.

An icon your browser displays perfectly can be one Google declines to show. That's a separate diagnosis with separate causes, and changing your <link> tags won't speed it up.

Debugging it in practice

When the wrong icon appears, in this order:

  1. Read the served HTML. curl -s https://yoursite.com | grep -i "rel=\"icon\|apple-touch\|manifest", or view-source:. Not the DevTools Elements panel — that shows the DOM after scripts have run, which can differ from what a crawler received. You're looking for declarations you didn't know were there.
  2. Request each declared file directly. Every one should return an image with a sensible content type. Anything returning HTML — which is what an SPA fallback does for unknown paths — is a candidate the browser is silently discarding.
  3. Check /favicon.ico even if you never declared it. It's requested regardless, and an old one at the root is the single most common cause of a stubborn wrong icon.
  4. Test in a private window, or a browser you've never opened the site in. This separates "cached" from "actually wrong" faster than any amount of cache-clearing.
  5. Reduce to one declaration per format. If you can't work out which of five candidates is winning, delete until only the one you want remains, confirm it works, then add back only what you need.

The Checker does steps one through three against a live URL — it lists every icon declaration in the served HTML, fetches each one, reports what actually loads, and flags the missing pieces for iOS and Android. It's the fastest way to find the declaration you'd forgotten was there, which is nearly always the answer.

Try the Checker

Point it at a URL and get a graded report of every icon a site declares — plus how each one looks on real devices.

Open Checker

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.