Favicon & App Icon ToolsVerifyBuild
Basics
Design

Dynamic Favicons: Show a Badge or Live Status

· 6 min read

That red badge on a browser tab is just a favicon being redrawn. Here is how to do it without a build step.

Why not just use a GIF?

You can set an animated GIF as a favicon, but support is uneven. Firefox animates it. Safari shows only the first frame. Chrome's behaviour has shifted across versions. That is fine for decoration, but it is the wrong tool for a live badge, because you cannot change what the GIF says at runtime.

For a count or a status that updates while the tab is open, you need to redraw the image in JavaScript. A canvas gives you a real pixel buffer you can paint into, then hand to the favicon link as a data URI.

How it works

  1. 1

    Load your base icon

    Draw your normal favicon image onto a small canvas, typically 32x32. This is the icon users see when there is nothing to badge.

  2. 2

    Paint the badge on top

    When there is a count, draw a small filled circle in a corner and render the number inside it. Keep it high-contrast so it reads at 32 pixels.

  3. 3

    Swap the favicon href

    Set the favicon link's href to the canvas output. The browser repaints the tab with your new image immediately.

  4. 4

    Update on change

    Re-run the draw-and-swap whenever the count changes, and redraw the plain icon when it drops to zero.

The swap, in plain JavaScript

function setFaviconBadge(count) {
  const canvas = document.createElement("canvas");
  canvas.width = 32;
  canvas.height = 32;
  const ctx = canvas.getContext("2d");

  // draw your base icon first (an <img> you preloaded)
  ctx.drawImage(baseIcon, 0, 0, 32, 32);

  if (count > 0) {
    ctx.fillStyle = "#e5484d";
    ctx.beginPath();
    ctx.arc(23, 9, 9, 0, Math.PI * 2);
    ctx.fill();

    ctx.fillStyle = "#ffffff";
    ctx.font = "bold 12px sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText(count > 9 ? "9+" : String(count), 23, 10);
  }

  let link = document.querySelector("link[rel~='icon']");
  if (!link) {
    link = document.createElement("link");
    link.rel = "icon";
    document.head.appendChild(link);
  }
  link.href = canvas.toDataURL("image/png");
}

baseIcon is an Image you loaded once. Call setFaviconBadge(unread) whenever the number changes. Cap the display at 9+ so two digits stay legible.

Common questions

How do I show an unread count in the browser tab?

Draw your icon and a badge with the number onto a canvas, then set the favicon link's href to canvas.toDataURL(). Re-run it whenever the count changes. There is no separate tab-badge API for regular web pages.

Can I use an animated GIF as a favicon?

You can, but support is inconsistent. Firefox animates it, Safari shows the first frame, and Chrome has varied. For a live badge that updates, use a canvas and swap the href instead.

Does a dynamic favicon hurt performance?

No. Drawing a 32x32 canvas and setting a data URI is cheap. Just redraw only when the value changes, not on a timer, and you will not notice the cost.

Keep reading