Give your users a tiny, delightful heads‑up—even when the tab is in the background. In this guide, you’ll learn pragmatic patterns to turn application state (unread counts, errors, recording, online/offline, progress) into dynamic favicons in Vue and Nuxt.
TL;DR: With VueUse’s useFavicon, you can dynamically change out favicons with ease. This alone is enough for most use cases but you can even combine with the canvas for completely custom favicons.
Common use cases:
useFavicon
VueUse ships with useFavicon, a composable that abstracts away favicon management. Instead of manually creating and replacing <link rel="icon">
, simply read and write to the reactive ref returned from useFavicon
.
<script setup lang="ts">
import { ref } from 'vue'
import { useFavicon } from '@vueuse/core'
// reactive favicon state
const icon = useFavicon()
console.log(icon.value) // /favicon.png
function alertMode() {
icon.value = '/favicon-alert.png'
}
function normalMode() {
icon.value = '/favicon.png'
}
</script>
This approach is perfect when you have a few pre‑rendered PNG/SVG favicons (base, alert, recording, offline, etc.).
Since useFavicon
is reactive, you can tie it directly to your Pinia or local component state.
Example with unread count:
<script setup lang="ts">
import { computed } from 'vue'
import { useFavicon } from '@vueuse/core'
import { useChatStore } from '@/stores/chat'
const chat = useChatStore()
const favicon = useFavicon()
watch(()=> chat.unreadCount, ()=>{
favicon.value = chat.unreadCount > 0
? '/favicon-alert.png'
: 'favicon.png'
})
</script>
Now the favicon swaps automatically whenever chat.unreadCount
changes.
Grabbing user attention with a flashing favicon is now as simple as changing out the favicon on a interval.
<script setup>
import { useFavicon, useInterval } from '@vueuse/core';
const favicon = useFavicon();
const favicons = {
default: '/favicon.png',
alert: '/favicon-alert.png',
};
favicon.value = favicons.default;
useInterval(1000, {
callback: () => {
favicon.value =
favicon.value === favicons.default
? favicons.alert : favicons.default;
},
});
</script>
You can get a live example of simple favicon changing and the interval example in this Stackblitz playground.
VueUse’s useFavicon
works best with static image URLs. But you can also customize the image precisely to the state with the canvas. For favicons like unread badges with an exact number count, you can generate a canvas image and then feed its data URL into the same composable.
First, you’ll want to create a function that draws an image to the canvas.
// src/utils/dynamic-favicon.ts
export function drawFaviconBadge(count: number): string {
const size = 64
const c = document.createElement('canvas')
c.width = c.height = size
const ctx = c.getContext('2d')!
// background circle
ctx.beginPath()
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2)
ctx.fillStyle = '#e11d48' // red
ctx.fill()
// label
const label = count > 99 ? '99+' : String(count)
ctx.fillStyle = '#fff'
ctx.font = `bold ${Math.floor(size * 0.5)}px system-ui, sans-serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(label, size / 2, size / 2 + 1)
// you could also print your images to the canvas for even more customization
// but we don't show that here
return c.toDataURL('image/png')
}
Then use that function to generate the image and set it as your favicon.
<script setup>
import { useFavicon, useInterval } from '@vueuse/core';
import { ref, watch } from 'vue';
const favicon = useFavicon();
import { drawFaviconBadge } from './utils/dynamic-favicon.ts';
const count = ref(0);
favicon.value = drawFaviconBadge(count.value);
watch(count, () => {
favicon.value = drawFaviconBadge(count.value);
});
</script>
<template>
<button @click="count++">Increment</button>
<button @click="count--">Decrement</button>
</template>
Nuxt plays nicely with useFavicon
. Just install the VueUse Nuxt module and everything else stays the same!
npm install @vueuse/nuxt @vueuse/core
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@vueuse/nuxt']
})
useFavicon
already replaces the link element, which helps, but cache‑busting (?v=123
) may be needed.favicon-dark.png
) and combine with VueUse useDark for the best output.VueUse’s useFavicon
makes dynamic favicons in Vue apps trivial. For simple cases, swap between static icons. For advanced cases, generate data URLs with canvas and let VueUse handle the rest. A tiny UX touch—yet your users will notice.
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.