Open Google in a desktop browser, search for almost anything technical, and look at the column on the left of every result. The little icons next to the domain names are favicons, and Google has been showing them in mobile SERPs since 2019 and in desktop SERPs since 2024. A site without a recognizable favicon there looks abandoned. A site with a sharp, on-brand favicon there looks polished. The mark is sixteen pixels wide and shows up next to your domain wherever a user is deciding whether to click.
That sixteen-pixel mark is also one of the few visual elements that survives across browser tabs, bookmark bars, OS taskbars, iOS home screens, Android PWA installs, RSS readers, and shortcut tiles. Each of those surfaces wants a slightly different file — different size, sometimes a different format, sometimes its own manifest entry. This guide covers what the modern set actually looks like, why a single favicon.ico is no longer enough, what each file does at runtime, and how the ZeroTool generator builds the package in your browser without uploading anything.
What modern browsers actually request
Open DevTools on your own site, refresh, and watch the network panel. A clean Chrome request hits at least these three:
| Request | Used by | Notes |
|---|---|---|
/favicon.ico | Legacy fallback for browsers and bookmark managers that look for the canonical filename first | A multi-size container; modern browsers keep asking for it for compatibility |
/<href from <link rel="icon" type="image/png">> | Modern browsers prefer a PNG with a matching sizes attribute | Chrome, Firefox, and Safari pick the closest size |
/<href from <link rel="apple-touch-icon">> | iOS Safari, both for the address bar and “Add to Home Screen” | Standard size is 180×180 |
Add a PWA install on Android Chrome and the picture grows:
| Request | Used by |
|---|---|
/site.webmanifest (or manifest.json) | Chrome, Edge, Brave, Samsung Internet — anywhere PWAs install |
192×192 and 512×512 PNGs declared in the manifest’s icons array | Android home-screen icon, Splash screen, Android share sheet |
Add purpose: "any maskable" to those manifest entries and Android applies a circular or rounded-square mask, depending on the launcher theme, so the icon looks native in the launcher grid. A maskable icon needs roughly 10% safe-zone padding around the visible mark, otherwise the launcher mask will clip the edges.
Then there is the long tail. Windows tiles used to ask for browserconfig.xml; Microsoft deprecated that in Windows 10 and the file is no longer needed. Safari’s pinned-tab mark uses a single-color SVG via <link rel="mask-icon">. Older Chrome on Android pre-PWA used apple-touch-icon as a fallback. None of those are mainstream needs in 2026, but they explain why old generators emit twenty-plus files when you actually want eleven.
Why a single favicon.ico is not enough
The historical default was to drop one ICO at the site root and let the browser figure it out. That worked when monitors topped out at 1280×1024 and tabs were 16-pixel squares. Two things broke it:
- Retina displays. A 16×16 ICO renders blurry on a 2× display when the browser scales it to 32 logical pixels. The
<link rel="icon" type="image/png" sizes="32x32">tag lets the browser pick the right resource for the device pixel ratio. - Operating system surfaces beyond the tab. iOS dock icons, Android home-screen tiles, PWA splash screens, Windows Edge tile icons — each surface samples a specific size from your set, and none of them want the 16-pixel ICO. They want 180, 192, 512, and a manifest that tells them what is what.
The fix is the modern set: a small ICO for compatibility, a few PNG sizes for the modern <link rel="icon"> chain, an apple-touch-icon for iOS, and a webmanifest with maskable PWA icons.
The full file list, explained
Here is what the ZeroTool generator emits and what each file does at runtime:
| File | Size(s) | Surface |
|---|---|---|
favicon.ico | 16+32+48 multi-size | Legacy browsers, bookmark managers, “request the canonical name first” code paths |
favicon-16.png | 16×16 | Browser tab on standard-DPI monitors |
favicon-32.png | 32×32 | Browser tab on retina monitors, some bookmark UIs |
favicon-48.png | 48×48 | Windows site shortcut, some search SERP previews |
favicon-64.png | 64×64 | Some address-bar UIs that want a denser mark |
favicon-96.png | 96×96 | Android Chrome shortcut without manifest install |
favicon-128.png | 128×128 | Older Chrome web app shortcuts |
apple-touch-icon.png | 180×180 | iOS Safari “Add to Home Screen”, iPad dock |
android-chrome-192.png | 192×192 | Android home screen via PWA install (manifest icon) |
android-chrome-512.png | 512×512 | Android splash screen, share sheet, Play Store-style cards |
site.webmanifest | text | Declares the 192/512 PNGs, theme color, and purpose: any maskable |
Eleven files, around 25 KB if your source is an emoji or simple SVG, around 200 KB if you push a 4K photo through a 512×512 reduce.
How the ICO format works
favicon.ico is the strangest file in the set because it predates PNG: it was born in Windows 3.0 and originally carried Device-Independent Bitmap (DIB / BMP) frames. Most current generators emit PNG-compressed entries instead, which Windows Vista’s shell, IE 11, and every browser since support natively. PNG-compressed entries are smaller, they preserve alpha, and the format is straightforward.
An ICO file is:
ICONDIR (6 bytes)
├── reserved (2 bytes, zero)
├── type (2 bytes, 1 = ICO, 2 = CUR)
└── count (2 bytes)
ICONDIRENTRY × count (16 bytes each)
├── width (1 byte, 0 means 256)
├── height (1 byte, 0 means 256)
├── colorCount (1 byte, zero for 32-bit)
├── reserved (1 byte, zero)
├── colorPlanes (2 bytes, 1)
├── bitsPerPixel (2 bytes, 32)
├── imageSize (4 bytes)
└── imageOffset (4 bytes — absolute offset into the ICO file)
(image payloads concatenated after the directory)
For PNG entries, the imageSize is the size of a complete PNG file (including its own IHDR / IDAT / IEND chunks) and the imageOffset points at the first byte of that PNG. The width/height fields in the ICONDIRENTRY can disagree with the PNG’s own width and height — what the OS displays is the PNG’s intrinsic size, so the ICONDIRENTRY values are advisory at best. Setting them to match keeps every shell happy.
The ZeroTool generator packs the 16, 32, and 48 PNGs into the ICO. It does not include the larger sizes because Windows tooling rarely picks anything larger than 48 from the legacy ICO container, and the larger PNGs are already exposed via the modern <link rel="icon"> chain and the manifest. Older recipes that put 256×256 in ICO date from when there was no apple-touch-icon; in 2026 the apple-touch-icon and android-chrome PNGs cover that territory.
How the ZIP container works
The browser needs a way to deliver eleven files in one click without firing eleven downloads, so the generator packs them into a ZIP. ZIP has been the default container for decades because every operating system can open it without third-party tooling. Inside, ZIP is layered: a sequence of local file headers each followed by a payload, then a “central directory” listing every entry, then an end-of-central-directory record telling readers where the central directory starts.
Each entry has two ways to encode payload bytes: STORED (no compression) or DEFLATE. PNG is already compressed, so re-running it through DEFLATE saves roughly 0.5–1% — not worth shipping a deflate implementation in the page bundle. The ZeroTool generator uses STORED for all entries and saves about five kilobytes of code that DEFLATE would have cost.
CRC-32 is the one piece of the ZIP spec you cannot skip. Every entry must carry the 32-bit CRC of its uncompressed bytes. The generator precomputes a 256-entry lookup table at script start and walks the bytes once per file. Filenames are flagged as UTF-8 in bit 11 of the General Purpose Bit Field, which keeps non-ASCII names readable in macOS Finder, Windows Explorer, and 7-Zip.
The Canvas pipeline
Drawing each PNG follows the same five steps:
// Pseudocode for one target size
const canvas = createCanvas(size, size);
const ctx = canvas.getContext('2d');
if (shape !== 'square') {
drawShapePath(ctx, shape, size);
ctx.clip(); // alpha mask for rounded / circle
}
if (bgMode === 'color') {
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, size, size);
}
const inner = size * (1 - padding * 2);
drawSourceCentered(ctx, source, size, inner); // emoji / text / image / svg
const pngArrayBuffer = await canvasToPng(canvas);
canvas.toBlob('image/png') does the heavy lifting — sizing, dithering, alpha encoding. The output is a complete PNG payload with IHDR, one or more IDAT chunks, and IEND. Most modern browsers default to a reasonable level of zlib compression inside the IDAT chunks; for a 16×16 mark you typically end up with 200–400 bytes per file.
There are three failure modes worth handling explicitly:
- Tainted canvas from cross-origin SVG content. If the user pastes SVG that loads
<image href="https://example.com/x.png">, the browser refuses to read pixels back as a blob. Wrapcanvas.toBlobin try/catch and surface a clear message: “SVG references external resources, please inline images first.” - Missing emoji font. Some IT-managed Linux distros ship without a color emoji font. The Canvas renders an empty rectangle and the resulting PNGs are blank squares. Detect via
ctx.measureText('🚀').width === 0and warn the user to switch to Image or SVG input. - Memory pressure with 4K images and nine target sizes. Sequentially generate, await each
toBlob, and revoke object URLs as soon as the bytes are read. Holding nine high-resolution canvases in memory simultaneously will crash Safari on iPad.
SVG, emoji, and the cross-platform consistency problem
The most subtle output difference is the color emoji font. macOS ships Apple Color Emoji, Windows ships Segoe UI Emoji, Android and Linux desktop most often ship Noto Color Emoji. The same Unicode codepoint renders three meaningfully different shapes:
- 🚀 in Apple Color Emoji is a yellow rocket with a thin white window
- 🚀 in Segoe UI Emoji is a flatter rocket with a darker window
- 🚀 in Noto Color Emoji has rounder edges and a different shadow
If you generate your favicon on macOS and someone visits your site from Windows, they still see the favicon you generated — the bytes are baked into the PNG. The Canvas renderer captures the emoji as drawn on the developer’s machine, then ships those pixels to every visitor. So pick a machine, generate once, and the result is consistent for every visitor afterward. Where consistency matters is the developer’s own iteration: regenerating six months later on a different OS will produce a slightly different favicon.
For pixel-identical output across machines, switch to the Image or SVG input. A vector mark in SVG renders to bitmap deterministically based on the SVG itself, not the OS font stack. The catch is that the SVG must be self-contained — no <image href> to remote resources, no external <use> fragments, no remote @font-face URLs. Inline everything before pasting.
What the HTML snippet does for you
The generator produces this snippet, which goes inside <head>:
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
<link rel="icon" type="image/png" sizes="48x48" href="/favicon-48.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="theme-color" content="#ffffff">
A few subtleties:
- The order matters less than you might think — modern browsers parse all
<link rel="icon">candidates and pick the best fit bysizesandtype. Putting the ICO first is convention, not requirement. <meta name="theme-color">colors the address bar on mobile Safari and Chrome and the title bar on installed PWAs. It is not a favicon, but it travels with the favicon set because it lives in the same head section and shares the brand-color decision.site.webmanifestis by convention; many tutorials usemanifest.json. Both work. Stick with whichever the rest of your project uses.- iOS does not use the manifest icons — it always picks
apple-touch-icon. Android Chrome prefers manifest icons overapple-touch-iconwhen both are present.
How ZeroTool compares to other generators
RealFaviconGenerator is the long-standing standard. It uploads your source image to a server, generates the package server-side with a wider sweep of legacy formats (Microsoft tile, Safari pinned-tab SVG, Windows 8 metro), and emits ~25 files. The trade-off is the upload step and the larger output.
favicon.io is closest in spirit to ZeroTool: simple inputs (emoji, text, image), small package, no upload. It does not currently emit a webmanifest.
The ZeroTool generator targets the 2026 baseline: eleven files, a webmanifest with maskable PWA icons, no server upload, and a copy-paste HTML snippet. If you need Microsoft Tile support or a Safari pinned-tab SVG, RealFaviconGenerator remains the right tool. If you need an emoji-driven favicon for a personal site or internal dashboard, ZeroTool is faster.
A pre-deploy checklist
Before you deploy the package, run through these:
- Drop the eleven files at the site root. Most hosts serve the file at
/favicon.icovia a 200 even without a<link>tag, so place them at the root rather than under/assets/icons/. - Paste the HTML snippet inside
<head>. The ICO is fetched implicitly, but the PNGs and apple-touch-icon need explicit<link>tags. - Update
site.webmanifestwith your site’snameandshort_name. The generator leaves them empty so you do not ship a placeholder name. - Test in DevTools’ Application panel. Application → Manifest shows the parsed manifest, the resolved icons, and any errors. Test maskable rendering with Chrome’s “Show maskable icon” toggle.
- Check the iOS path. Add to Home Screen on a real iPhone (or in iOS Simulator) and verify the apple-touch-icon shows up without a thin white border. iOS auto-adds rounded corners but does not auto-apply background — if your icon has a transparent background, iOS fills it with white, which can look strange against dark wallpapers.
- Verify the Google SERP favicon. Google needs at least 8×8 and recommends at least 48×48; the multi-size ICO handles both. Use Google’s Favicon Test Tool (Coverage → “Favicons” report) to confirm.
- Regenerate when the brand changes. Treat the favicon set as a build artifact, not a one-off. Keep the source file (the SVG or the high-resolution PNG you fed in) in version control, and regenerate from the same source whenever the mark or theme color changes.
Privacy notes
Because the entire pipeline runs in your browser, no version of your source image leaves your machine. This matters more than it sounds: when you generate a favicon for an unreleased product, you are leaking the brand mark to whichever generator you use. RealFaviconGenerator’s privacy policy is straightforward but the upload still happens. ZeroTool keeps the image local. Open DevTools → Network and click Generate — there is exactly one outbound request, and it is a non-blocking analytics ping with the tool name in the payload.
Further reading
- Apple’s “Configuring web content” guide on apple-touch-icon sizing
- W3C Web App Manifest specification — the source of truth for manifest icon
purposevalues - web.dev: Maskable icons in PWAs
- Microsoft’s deprecation of
browserconfig.xml— confirmation that you no longer need MS Tile assets - Google Search Central: Define a favicon to show in search results
- Apache ZIP file format specification (PKZIP APPNOTE.TXT) — for anyone who wants to read the full ZIP layout
Related tools on ZeroTool: SVG to PNG Converter, WebP Converter, Image to Base64, QR Code Generator.