How-to7 min read

How to Make a Favicon from an Icon

A minimal, current favicon setup: an SVG, a multi-size ICO, an Apple touch icon, and manifest icons.Includes a tested Node script that generates all of them from one SVG.

Sanchez Kim·Published June 9, 2026·Updated October 15, 2026

Favicon generators often hand you 20 files and a block of markup to paste. You need about five.

Here's which ones, why each exists, and how to design an icon that holds up at 16 pixels. At the end there's a short Node script, tested while writing this, that builds all of them from a single SVG, including a real multi-size .ico file, with no online generator.

Who asks for your favicon, and in what format

  • Browser tabs, bookmarks, and history read <link rel="icon">. With no link at all, browsers fall back to requesting /favicon.ico from the site root.
  • SVG favicons work in Chrome and Edge 80+ and Firefox 41+, but, according to caniuse.com as of October 2026, Safari only added support in version 26 on both macOS and iOS. Older Safari versions still in use need a raster fallback.
  • Google Search shows a favicon next to results. Google's favicon guidelines list BMP, GIF, ICO, PNG, JPEG, PPM, and TIFF as supported formats and don't list SVG. They require a square icon at least 8×8, recommends larger than 48×48, and needs both the home page and the icon file to be crawlable. The favicon has to be declared on the home page.
  • iOS "Add to Home Screen" uses rel="apple-touch-icon", a 180×180 PNG.
  • Installed web apps (Android, desktop PWAs) read icons from the web app manifest: 192×192 and 512×512.

So the minimal set that covers all of these is: favicon.svg, favicon.ico (16, 32, and 48px inside one file), apple-touch-icon.png, and, if you want the site to be installable, two manifest PNGs.

Step 1: design for 16 pixels first

A favicon spends most of its life at 16×16 or 32×32 device pixels. That changes which icons work.

I rasterized Lucide's outline "house" icon (24-unit grid, 2-unit stroke) at 16px and counted pixels: 98.4% of its visible pixels were partially transparent, meaning almost nothing lands as solid color. At 48px that figure drops to 22.3%.

Phosphor's filled house at the same 16px came out at 28.8%. The same filled house in white on the Favicon Generator's default tile measured 51.2%: the 12% padding shrinks the glyph to about 12 px, so more of its edge falls between pixels. The tile is there for contrast against the tab bar, not to make the edges sharper.

Three 16-pixel favicons enlarged so each pixel is visible. The Lucide outline house is made of gray, half-transparent pixels. The Phosphor filled house is mostly solid dark pixels with a soft rim. The same filled house in white on a purple rounded tile has soft edges around a smaller shape.
Each rasterized at 16×16 with sharp (librsvg) and enlarged 12×. Partial = share of drawn pixels with 0 < alpha < 255. For the tile, the white glyph was measured on its own at its position in the tile (Favicon Generator defaults: #4f46e5, 25% radius, 12% padding).

Thin outline icons that look sharp in a toolbar turn into gray smudges in a tab.

  • Use a filled (solid) icon, not an outline. Filled shapes have large solid areas that survive at small sizes.
  • Put it on a tile. A solid rounded square behind the icon guarantees contrast on both light and dark tab bars. Many well-known favicons use this pattern.
  • One shape, no text. Letters are unreadable at 16px unless the whole favicon is a single letter.
  • Leave some margin, but not much. Around 10–15% on each side keeps it from looking cramped while using most of the tiny area.

A tile-style SVG favicon can be this small:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
  <!-- Solid tile: readable on light and dark tab bars alike -->
  <rect width="48" height="48" rx="12" fill="#4f46e5" />
  <!-- The icon, filled, occupying roughly the middle 70% -->
  <path d="M24 8 40 24 24 40 8 24Z" fill="#ffffff" />
</svg>

For a real-world reference, Icony's own favicon.svg follows this pattern: a 48×48 rounded tile with a gradient fill, a white four-pointed star, and two small semi-transparent white dots. The file is 637 bytes, 378 bytes with gzip at level 9.

Starting from an icon library? Pick the filled variant (for example Phosphor's fill weight or Heroicons solid), set its color, and export it as SVG. In Icony you can do that in one step and then wrap the result in a tile as shown above.

Step 2 (optional): adapt to dark mode

An SVG favicon can contain a prefers-color-scheme media query, so a dark mark can turn light when the browser is in dark mode:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <style>
    .mark { fill: #111827; }
    @media (prefers-color-scheme: dark) {
      .mark { fill: #f9fafb; }
    }
  </style>
  <path class="mark" d="M16 3 29 16 16 29 3 16Z" />
</svg>

Browser support for media queries inside favicons isn't uniform, and the ICO and PNG fallbacks can't adapt at all. The tile approach from Step 1 looks right in both themes without any of this, so I'd try it first.

Step 3: generate the raster files from the SVG

You don't need an online generator for this. The script below uses sharp to rasterize the SVG at each size, and writes the .ico container itself. An ICO file is a 6-byte header, one 16-byte directory entry per image, then the image data, and each image can simply be a PNG.

// npm i sharp   then: node make-favicons.mjs   (reads favicon.svg)
import sharp from 'sharp';
import { readFile, writeFile } from 'node:fs/promises';

const svg = await readFile('favicon.svg');
const png = (size) => sharp(svg).resize(size, size).png().toBuffer();

// Apple touch icon: give it a solid background, no transparency
await writeFile('apple-touch-icon.png',
  await sharp(svg).resize(180, 180).flatten({ background: '#ffffff' }).png().toBuffer());

// Web app manifest icons
await writeFile('icon-192.png', await png(192));
await writeFile('icon-512.png', await png(512));

// favicon.ico = 6-byte header + 16-byte directory entry per image + PNG data
const sizes = [16, 32, 48];
const images = await Promise.all(sizes.map(png));
const header = Buffer.alloc(6);
header.writeUInt16LE(0, 0);             // reserved
header.writeUInt16LE(1, 2);             // type: 1 = icon
header.writeUInt16LE(images.length, 4); // number of images
let offset = 6 + 16 * images.length;
const entries = images.map((img, i) => {
  const e = Buffer.alloc(16);
  e.writeUInt8(sizes[i] % 256, 0);      // width  (0 means 256)
  e.writeUInt8(sizes[i] % 256, 1);      // height (0 means 256)
  e.writeUInt16LE(1, 4);                // color planes
  e.writeUInt16LE(32, 6);               // bits per pixel
  e.writeUInt32LE(img.length, 8);       // size of this image's data
  e.writeUInt32LE(offset, 12);          // where the data starts
  offset += img.length;
  return e;
});
await writeFile('favicon.ico', Buffer.concat([header, ...entries, ...images]));

I ran this script on Icony's own 637-byte favicon.svg. file identified the output as "MS Windows icon resource - 3 icons, 16x16 with PNG image data…", and macOS sips reads it without complaint.

The output sizes were: favicon.ico 3,036 B (all three sizes), apple-touch-icon.png 4,953 B, icon-192.png 6,250 B, icon-512.png 21,246 B. With sharp 0.34.5, resize() re-renders the vector at each target size, so the 16px image is drawn fresh, not downscaled from a large bitmap.

Reading the ICO back shows what that means at each size. In the 16px frame the star survives, but each small dot is reduced to a couple of slightly lighter pixels. That's the kind of detail the next tip is about.

The three images stored inside the generated favicon.ico, each enlarged to the same width: a 16 by 16 frame where the star is a blurry cross and the two small dots are barely visible, a 32 by 32 frame where the dots appear as soft specks, and a 48 by 48 frame with a clean star and both dots.
The three PNG frames read back out of the favicon.ico the script produced from Icony's favicon.svg (sharp 0.34.5), enlarged to the same width with nearest-neighbor scaling. The byte counts are each frame's PNG data.

If your icon has detail that disappears at 16px, you can make a simplified SVG just for the 16 and 32px ICO entries and use the full version for larger sizes. Just pass a different input to the png() helper for those sizes.

Skip the script: build all of these files from any icon in your browser with the Favicon Generator.

Step 4: add the markup

Put the files in your site root and add this to the <head> of every page, or at least the home page:

<!-- Raster fallback for browsers without SVG favicon support -->
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<!-- Vector icon for browsers that support SVG favicons -->
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<!-- iOS / iPadOS home screen -->
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Installable web app icons (Android, desktop PWAs) -->
<link rel="manifest" href="/manifest.webmanifest" />

Browsers that support SVG favicons use favicon.svg. The others fall back to the ICO. Google's favicon guidelines list ICO and PNG as supported formats but not SVG, so shipping an ICO covers Search too. Keep favicon.ico at the root even with the <link> in place, because some tools still request that path directly.

Step 5 (if installable): the manifest

{
  "name": "Example App",
  "short_name": "Example",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icon-maskable-512.png", "sizes": "512x512",
      "type": "image/png", "purpose": "maskable" }
  ]
}

A maskable icon is one the operating system may crop into a circle, squircle, or rounded square. The spec defines a safe zone: a centered circle with a radius of 40% of the icon's width. Keep everything important inside it and extend the background color to the edges.

Your normal 512px icon, with the mark reaching the corners, usually gets clipped if you mark it maskable, so generate a separate version with more padding.

Icony's own favicon is a small example: its star fits inside the safe zone, but the two accent dots don't. The Favicon Generator's maskable file extends the tile color to the edges and scales the mark down (to 0.744 of its size with the default 12% padding) so the mark's box fits inside the circle.

Three app icons with a dashed orange circle marking the maskable safe zone. Icony's favicon: the star fits inside the circle but both small white dots sit outside it. The Favicon Generator's icon-512 with a white house on a purple rounded tile: the house is inside the circle. Its icon-maskable-512: a full-bleed purple square with a smaller house well inside the circle.
Dashed circle: the safe zone, radius 40% of the icon width. Measured at 512 px on the white mark alone: 6.6% of the pixels in Icony's favicon.svg fall outside it (the two accent dots); 0% for the generator's house, in both files.

The manifest's file name is up to you, as long as <link rel="manifest"> points to it. This guide uses manifest.webmanifest; Icony's Favicon Generator names the same file site.webmanifest.

If you use Next.js (App Router)

Next.js generates the <link> tags for you when you place specially named files in the app/ directory:

app/
├── favicon.ico      → <link rel="icon" href="/favicon.ico" …>
├── icon.svg         → <link rel="icon" href="/icon.svg?…" type="image/svg+xml">
├── apple-icon.png   → <link rel="apple-touch-icon" href="/apple-icon.png?…">
└── layout.tsx

favicon.ico only works at the top level of app/. Next appends a generated query string to the icon and apple-icon URLs, which helps with cache busting when you change them.

Mistakes that break or blur a favicon

  • Only shipping an SVG. It looks fine in Chrome and Firefox, but older Safari shows a generic icon, and SVG isn't among the formats Google's favicon guidelines list for Search.
  • A PNG renamed to .ico. Many browsers will cope, but it's not an ICO, and strict consumers may reject it. Run file favicon.ico to confirm.
  • A transparent Apple touch icon. iOS doesn't keep transparency on home-screen icons, and transparent areas commonly end up black. Flatten the icon onto a background color, as the script does.
  • Blocking the icon from crawlers. If robots.txt disallows the path, or the icon sits behind authentication, Google can't use it.
  • Declaring the favicon only on inner pages. Google reads it from the home page.
  • Outline icons with 1–2px strokes. They fade at 16px, as the 98.4% figure above shows. Use filled shapes.
  • Scaling a single 512px PNG down in the browser. Tiny downscaled bitmaps lose detail unpredictably. Rasterizing the vector at each size gives the renderer a fair chance.

Verify it

file favicon.ico
# favicon.ico: MS Windows icon resource - 3 icons, 16x16 with PNG image data, …

curl -sI https://example.com/favicon.ico | grep -i content-type
curl -s https://example.com/robots.txt   # make sure the icon path isn't disallowed
  • Open the site in a private window. Browsers cache favicons aggressively and separately from the page, so a normal reload often keeps showing the old icon. Adding a version to the URL (/favicon.svg?v=2) forces a fresh fetch.
  • Check a light and a dark tab bar, and look at the pinned-tab or bookmarks-bar size.
  • Add the site to an iPhone home screen once to check the touch icon.
  • For Google Search, use the URL Inspection tool in Search Console to request a recrawl of the home page. The icon updates on Google's own schedule, not right away.

FAQ

Do I still need browserconfig.xml and mstile images? They were for pinned sites in legacy versions of Windows and Internet Explorer. Most sites today can skip them.

Do I need separate 16×16 and 32×32 PNG links? Not if you ship an ICO that contains both. That's the point of the multi-size container.

Can the favicon be animated? Some browsers animate GIF favicons, and scripts can swap the icon at runtime (for example to show an unread count), but support is inconsistent, and a static icon is the safe choice for places like home screens and search results.

Try it in Icony

Search 10,000+ open-source icons, customize color, size, and stroke, then copy or download as SVG, PNG, or a React component — free.

Open the icon customizer

Related guides