Dynamic Favicons for Light and Dark Themes
Animate those icons. Brand that scrollbar. Polish that :active state. Load a single typeface glyph to render the world’s best ampersand. Make it fast. Make it accessible. Add keyboard shortcuts. Add tooltips. Animate the transition between two open tooltips. Manually measure text and avoid line wrapping widows.Design favicons that reflect app state. — Paco Coursey
This line is what pushed me to try this out. If you toggle the theme on this page, the favicon switches too. The color inversion is intentional. It keeps the icon visible in both light and dark themes.
I already have theme switching in place, so adding dynamic favicons is pretty straightforward.
Updating favicon when user toggles theme:
const root = document.documentElement;
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const toggle = document.getElementById("theme-toggle");
let favicon = document.querySelector('link[rel*="icon"]');
// Function to apply theme from system
function applyTheme(e) {
if (localStorage.getItem("theme")) return;
if (e.matches) {
root.classList.add("dark");
favicon.href = "/favicon-darj.ico";
} else {
root.classList.remove("dark");
favicon.href = "/favicon-light.ico";
}
}
// Initial system set
applyTheme(mediaQuery);
// Watch for live OS theme changes
mediaQuery.addEventListener("change", applyTheme);
// Manual toggle
toggle?.addEventListener("click", () => {
const isDark = root.classList.toggle("dark");
favicon.href = isDark ? "/favicon-dark.ico" : "/favicon-light.ico";
localStorage.setItem("theme", isDark ? "dark" : "light");
});
This covers two cases;
- User manually toggles the theme
- System preference changes while the page is open
Next we also update the favicon before the page paints to ensure the correct favicon is being used:
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const savedTheme = localStorage.getItem("theme");
let favicon = document.querySelector('link[rel*="icon"]');
if (!savedTheme) {
if (prefersDark) {
document.documentElement.classList.add("dark");
favicon.href = "/favicon-light.ico";
}
} else if (savedTheme === "dark") {
document.documentElement.classList.add("dark");
favicon.href = "/favicon-light.ico";
}
Done.