UPDATE: Whilst the method below still works absolutely fine, SVG favicon support is much more prevalent in browsers nowadays. The same functionality as below can be accomplished with a single SVG favicon and a prefers-color-scheme
media query within it. See this post by Thomas Steiner for more info.
If you do still need to use separate ico
files, you can accomplish the same thing as below, but without any JavaScript, by using the following snippet:
<link rel="icon" href="/favicon.ico" media="(prefers-color-scheme:no-preference)">
<link rel="icon" href="/favicon-dark.ico" media="(prefers-color-scheme:dark)">
<link rel="icon" href="/favicon.ico" media="(prefers-color-scheme:light)">
For an older method using JavaScript, continue reading below.
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.
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.
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.