---
title: Dynamic Favicons: Show a Badge or Live Status
description: How to show an unread count or live status in your favicon. Why GIFs are unreliable and how to draw a badge on a canvas and swap the favicon in JavaScript.
canonical: https://favicontools.com/blog/dynamic-favicon-guide
---

# Dynamic Favicons: Show a Badge or Live Status

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

> **Short answer:** Draw your icon plus a badge onto a hidden <canvas>, then point the favicon <link> at the canvas output with link.href = canvas.toDataURL(). Re-run it whenever the count changes. This works everywhere and does not depend on animated GIF support.

## 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. **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. **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. **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. **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

```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.

> **Keep one icon element:** If the tab icon stops updating, you probably have more than one <link rel="icon"> and the browser is reading a different one. Query the existing link and reuse it instead of appending a new tag each time. See favicon not showing if the base icon never appears at all.

## 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.
