Updating your website favicon dynamically with dark mode

Dark mode is everywhere now (and I love it). It's in Windows 10, Mac OS Mojave, iOS 13, and Android 10, the vast majority of which support the prefers-color-scheme media query.

Many websites have already implemented dark themes by using this media query, but with browsers like Chrome, website favicons often get forgotten about, resulting in illegible favicons on some sites with dark mode.

For example, take a look at how my website's favicon looked when in dark mode in Chrome 78, on Windows 10. You can see the J from my favicon is pretty much illegible due to the dark tab background, compared to a lighter tab background in light mode.

Comparing the Chrome dark vs light mode UI with static favicon

Dynamically switching favicon

My blog injects the following favicons into my page. These are the two favicons we want to update to dark mode, when enabled.

<link rel="icon" type="image/png" href="/assets/images/favicon-192.png" sizes="192x192">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

For this example, I've added a favicon-dark.ico and /assets/images/favicon-dark-192.png to my blog for use when in dark mode.

To update these dynamically, we'll need to use a little JavaScript, and the window.matchMedia API. This works pretty much identically to a media query within CSS.

var darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
handleDarkmode(darkModeMediaQuery);
function handleDarkmode(e){
	var darkModeOn = e.matches; // true if dark mode is enabled
	var favicon = document.querySelector('link[rel="shortcut icon"]'); // get favicon-192.png element
	var largeFavicon = document.querySelector('link[rel="icon"]'); // get favicon.ico element
	if(!favicon || !largeFavicon){
		return; // where are our favicon elements???
	}
	// replace icons with dark/light themes as appropriate
	if(darkModeOn){
		favicon.href = '/favicon-dark.ico';
		largeFavicon.href = '/assets/images/favicon-dark-192.png';
	}else{
		favicon.href = '/favicon.ico';
		largeFavicon.href = '/assets/images/favicon-192.png';
	}
}
darkModeMediaQuery.addListener(handleDarkmode);

As you can see from the above code, I first check if dark mode is enabled via window.matchMedia('(prefers-color-scheme: dark)'). I then define a function, handleDarkmode which will automatically update our favicons to dark variants, when dark mode is enabled.

I run this function immediately on page load with the original result of the dark mode query, and then setup a listener via darkModeMediaQuery.addListener(handleDarkmode), which will ensure the favicon is continually updated every time the result of this media query changes. Watch the favicon in the gif below to see it update dynamically as the system colour scheme changes.

Dynamic favicons in Chrome with dark/light mode UI
Dynamically updating favicon with dark/light mode

You could extend this to also update things like theme-color and your apple-touch-icon etc. but this was "good enough" for my use-case.