Dynamic Favicons: A Practical Guide

Most sites treat the favicon as a static asset—set once in the <head> and forgotten. But the favicon is just an image source, and nothing stops you from updating it at runtime. Some sites use this for notifications, swapping in a red dot or badge when something needs attention. The same mechanism can do a lot more. Here's a clean way to make the favicon reflect the current time, using nothing but a canvas-generated data URL and a bit of JavaScript—plus a React-friendly wrapper for component-based projects.

Generating an emoji favicon

The core trick is drawing the emoji onto a canvas and exporting it as a data URL. This function takes an emoji and returns a valid image source:

// Thanks to https://formito.com/tools/favicon
const faviconHref = emoji =>
  `data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22256%22 height=%22256%22 viewBox=%220 0 100 100%22><text x=%2250%%22 y=%2250%%22 dominant-baseline=%22central%22 text-anchor=%22middle%22 font-size=%2280%22>${emoji}</text></svg>`

To actually swap the favicon, target the existing <link rel="icon"> element and replace its href attribute. If that link doesn't exist, create it first:

const changeFavicon = emoji => {
  // Ensure we have access to the document, i.e. we are in the browser.
  if (typeof window === 'undefined') return

  const link =
    window.document.querySelector("link[rel*='icon']") ||
    window.document.createElement("link")
  link.type = "image/svg+xml"
  link.rel = "shortcut icon"
  link.href = faviconHref(emoji)

  window.document.getElementsByTagName("head")[0].appendChild(link)
}

You can test both functions immediately in your browser's DevTools console. Paste them in and call changeFavicon("💃")—the favicon will update on the spot.

Mapping the time to an emoji

Clock emojis exist for each hour and half-hour mark. To pick the right one, round the current time to the nearest half-hour. For instance, anything from 9:45 to 10:14 should show the 10:00 clock; 10:15 to 10:44 shows the 10:30 clock. This function handles that mapping:

const currentEmoji = () => {
  // Add 15 minutes and round down to closest half hour
  const time = new Date(Date.now() + 15 * 60 * 1000)

  const hours = time.getHours() % 12
  const minutes = time.getMinutes() < 30 ? 0 : 30

  return {
    "0.0": "🕛",
    "0.30": "🕧",
    "1.0": "🕐",
    "1.30": "🕜",
    "2.0": "🕑",
    "2.30": "🕝",
    "3.0": "🕒",
    "3.30": "🕞",
    "4.0": "🕓",
    "4.30": "🕟",
    "5.0": "🕔",
    "5.30": "🕠",
    "6.0": "🕕",
    "6.30": "🕡",
    "7.0": "🕖",
    "7.30": "🕢",
    "8.0": "🕗",
    "8.30": "🕣",
    "9.0": "🕘",
    "9.30": "🕤",
    "10.0": "🕙",
    "10.30": "🕥",
    "11.0": "🕚",
    "11.30": "🕦",
  }[`${hours}.${minutes}`]
}

With the helper in place, a setInterval keeps the favicon current:

// One minute
const delay = 60 * 1000

// Change the favicon when the page gets loaded...
const emoji = currentEmoji()
changeFavicon(emoji)

// ... and update it every minute
setInterval(() => {
  const emoji = currentEmoji()
  changeFavicon(emoji)
}, delay)

That's all it takes for a vanilla JavaScript approach.

Bringing it into React

The imperative nature of this code—directly manipulating the DOM and running on an interval—doesn't map neatly to React's declarative model. The solution is a custom hook that encapsulates the side effect. Dan Abramov's article on making setInterval declarative explains the pattern: use useEffect to set up the interval and useState to hold the current emoji. Here's how it looks:

import { useEffect } from "react"
import useInterval from "./useInterval"

const delay = 60 * 1000

const useTimeFavicon = () => {
  // Change the favicon when the component gets mounted...
  useEffect(() => {
    const emoji = currentEmoji()
    changeFavicon(emoji)
  }, [])

  // ... and update it every minute
  useInterval(() => {
    const emoji = currentEmoji()
    changeFavicon(emoji)
  }, delay)
}

Calling useTimeFavicon() from the root component is enough to activate the behaviour site-wide.

Working with borrowed blocks

This example is a small case of a familiar engineering principle: break a problem into pieces, and re-use existing solutions where they fit. The canvas-to-data-URL conversion, the link manipulation, and the time-rounding logic each came from separate sources and were combined with minimal glue. The result is a favicon that updates itself every minute—whether you're using it for time, notifications, or something else entirely—without reinventing any of the underlying machinery.