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.
Browsers don't use the first icon you declare, or the last one. They collect every candidate, drop the ones they can't display, and pick the best match for the size they're drawing. Only when a page declares no icon at all do they try /favicon.ico.
That explains a surprising number of "it's showing the wrong icon" problems. You add a new icon, the old one keeps appearing, and nothing is wrong with your markup. The browser is making a choice, just not the way you assumed. Two practical consequences: an unwanted icon usually means a stale declaration you forgot about, and the sizes and type attributes are how you steer the choice.
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:
- 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.
- 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.
- 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.
- 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.
- Fall back to
/favicon.icoif the page declared no icons at all. In my tests, Chrome skipped this request entirely when a page declared an icon, and it didn't fall back when the declared icon was broken either: it just showed no icon.
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 /apple-touch-icon.png, then a page snapshot |
| 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:
- Read the served HTML.
curl -s https://yoursite.com | grep -i "rel=\"icon\|apple-touch\|manifest", orview-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. - 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.
- Check
/favicon.icoeven if you never declared it. Pages without icon tags and many non-browser tools still use it, and browsers may have cached it from an earlier visit, so an old one at the root is a common cause of a stubborn wrong icon. - 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.
- 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 CheckerWritten by Elabid Asmaa
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.