Contents
  1. The basic technique
  2. Generating an icon at runtime
  3. Choosing an approach
  4. Preparing logo artwork for favicon variants
  5. Limitations to check before relying on it
  6. Evidence gaps

A browser takes a page's favicon from a link element in the document head whose rel attribute contains "icon". To change the favicon dynamically, JavaScript updates that element's href, or replaces the element, so it points to a different image. The browser then loads and shows the new icon in the tab. The new image can be a prepared SVG or PNG file, or an image generated in the page, such as a canvas drawing with a notification badge. No library is required.

The basic technique

The simplest approach finds the existing icon link, creates one if none exists, and sets a new href. The type attribute tells the browser what format to expect, which matters when you switch between SVG and PNG variants.

function setFavicon(href, type) {
  let link = document.querySelector('link[rel~="icon"]');
  if (!link) {
    link = document.createElement('link');
    link.rel = 'icon';
    document.head.appendChild(link);
  }
  if (type) link.type = type;
  link.href = href;
}

Many sites declare more than one icon, for example an SVG plus a PNG or ICO fallback. The browser chooses among them, so updating only the first match may not change the visible icon. Some browsers also do not refresh the tab icon reliably when only the attribute changes. Removing every icon link and inserting a fresh one avoids both problems:

function replaceFavicon(href, type) {
  document.querySelectorAll('link[rel~="icon"]').forEach((el) => el.remove());
  const link = document.createElement('link');
  link.rel = 'icon';
  if (type) link.type = type;
  link.href = href;
  document.head.appendChild(link);
}

Generating an icon at runtime

Some states can't be prepared as files in advance, such as an unread count. For these, draw the base icon onto a canvas, add the dynamic element, and convert the result to a data URL that can be used as the href.

function drawBadgeFavicon(baseImage, count) {
  const size = 64;
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(baseImage, 0, 0, size, size);
  if (count > 0) {
    ctx.fillStyle = '#d93025';
    ctx.beginPath();
    ctx.arc(size * 0.72, size * 0.28, size * 0.28, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#ffffff';
    ctx.font = `bold ${size * 0.36}px sans-serif`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(count > 9 ? '9+' : String(count), size * 0.72, size * 0.29);
  }
  return canvas.toDataURL('image/png');
}

const img = new Image();
img.onload = () => replaceFavicon(drawBadgeFavicon(img, 3), 'image/png');
img.src = baseIconPath; // your own same-origin icon file

The canvas output is a raster PNG, even when the source drawn onto it was an SVG. That is fine at tab-icon sizes, but the generated image is not vector artwork. Also, the base image must be same-origin or served with appropriate cross-origin permissions. Otherwise the canvas becomes tainted and the browser refuses to export it.

Choosing an approach

Approach | Output | Needs JavaScript | Suited to
Swap between prepared icon files | Whatever format the files are (SVG, PNG, ICO) | Yes | A small, fixed set of states such as default, alert, offline or staging
Generate on a canvas | Raster PNG data | Yes | Values that change freely, such as counts or progress
Single SVG with an internal color-scheme media query | Vector SVG | No | Light and dark variants that follow the user's system theme

The last row is worth noting because it is often the actual goal behind a dynamic favicon. An SVG favicon can contain its own style rules, including a prefers-color-scheme media query, so the icon adapts to light or dark mode with no script at all. JavaScript is only needed when the icon has to reflect application state.

Preparing logo artwork for favicon variants

Swapping between prepared files puts the design work into the icon set itself. A favicon is shown very small, so a full logo usually needs to be reduced to a mark that stays legible at that scale. The steps below are general practice, not measured recommendations.

  • Start from a square artboard and a simplified mark, such as a symbol or monogram, rather than the full horizontal lockup.
  • Convert live text to outlines in the SVG so the icon does not depend on fonts the visitor may not have, provided the font license permits outlining.
  • Check that the SVG actually contains vector paths. A file with an .svg extension can simply wrap an embedded raster image, which gives none of the scaling benefits of vector artwork.
  • Build each state, such as default, alert or dark, as a separate file from the same master artwork so edges and proportions stay consistent between swaps.
  • Keep a raster fallback (PNG or ICO) for browsers and contexts that do not use SVG icons.

If the favicon is based on someone else's logo, being able to download the file does not grant permission to alter it. Recoloring a mark, adding badges, or changing the type can conflict with brand guidelines, font licenses or trademark rights. Confirm what modifications are allowed before producing variants.

Limitations to check before relying on it

  • Browser support for SVG favicons has varied, and Safari in particular has lagged behind other engines. Check current compatibility data rather than assuming SVG will be used everywhere.
  • The script only changes the tab icon for the open page. Bookmarks, home-screen shortcuts and pinned tabs typically use other assets, such as touch icons or web app manifest icons, which the script does not update.
  • Search engines and link previews read the favicon declared in the page markup or at the site root, not one set later by script.
  • Browsers may throttle timers in background tabs, so an icon updated on an interval can lag while the tab is hidden.
  • A favicon change is easy to miss and is not announced to screen readers. Pair it with a title change or an in-page indicator if the information matters.

Evidence gaps

No tested browser-version matrix, performance measurements or vendor documentation were supplied for this article. The behavior described here reflects widely documented web platform principles, not verified results for specific browser releases. Before shipping, test the swap in the browsers your audience uses, especially the SVG path and the remove-and-reinsert fallback.