Favicon dark mode

Be careful with your favicon! If you use transparency within a PNG or you’re already using an SVG favicon with no background, consider what happens when your users switch from light to dark mode.

My simple ‘D’ favicon is a black SVG path with no background. So it would be invisible in dark mode, when most apps/browsers make the area behind the icon black. It’s a black ‘D’ on a black background.

If you’re set on using a PNG file, I’m afraid there isn’t a perfect solution for you. You’ll just need to remove the transparency and add a background to your image.

But for SVG favicons, there is a solution.

Plus, an SVG favicon are likely to be smaller than your PNGs, you only need one SVG because it scales to different sizes properly as a vector, and you can use media queries!

The media query part is the fix in my favicon.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
    <style>
        path { fill: black }
        @media (prefers-color-scheme: dark) { path { fill: white } }
    </style>
    <path d="M19.7 44.2H9.8V5.8h9.9v38.4zm-5.7 0l.1-8h8.4c1.7 0 3.1-.4 4.2-1.2s2-1.9 2.6-3.5.9-3.5.9-5.9v-1.3c0-2.4-.3-4.3-.9-5.9s-1.5-2.7-2.6-3.5c-1.2-.8-2.6-1.2-4.4-1.2h-8.5v-8h8.5c3.5 0 6.6.8 9.3 2.4s4.8 3.8 6.3 6.6 2.3 6 2.3 9.6v1.3c0 3.6-.8 6.8-2.3 9.6s-3.6 5-6.3 6.6-5.7 2.4-9.2 2.4H14z" />
</svg>

I know it doesn’t really matter because it get cached, by my favicon is 487 bytes file size. Much better than an image (though I do use an image for larger favicons for iOS and Android, admittedly).

Here’s how it works. The path { fill: black } part refers to the black ‘D’ in light mode and the prefers-color-scheme: dark part just afterwards, refers to the white ‘D’ in dark mode.

I did run into a problem with the favicon not changing with the theme. If a user first accessed my website in dark mode, they’d get the dark mode version. The same with the light mode version.

But if a user switched between light and dark mode with the website opened nothing would happen until a hard refresh. And, my website is build in Next.js so changing between pages doesn’t trigger a reload either.

This works as a fix by forcing the favicon to reload:

window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",function(e) {
	if(e.matches) {
		document.getElementById("favicon").href="/favicon.svg?dark";
	}
	else {
		document.getElementById("favicon").href="/favicon.svg?light";
	}
})

Share