Aller au contenu principal
~/GiwiSoft
529ivn529ivn529i

Favicon avec thème clair/sombre

Giwi 3 min de lecture Web, JavaScript

Tout le monde n’utilise pas son OS ou son navigateur en thème sombre, tout le monde n’a pas son côté gothique twighlight.

Cependant, des fois, le favicon de notre site Web se marie mal avec un thème light ou un thème dark (voire les 2, mais là, je ne peux rien faire).

Favicon wikipedia

L’idée est de d’avoir un favicon dédié mode sombre et un dédié mode clair et d’utiliser celui qui est en phase avec le thème utilisé par notre internaute.

Ce se fait avec l’attribut média de la balise link :

<link rel="icon" type="image/png" href="/favicon-light.png" media="(prefers-color-scheme:light)">
<link rel="icon" type="image/png" href="/favicon-dark.png" media="(prefers-color-scheme:dark)">
<link rel="icon" type="image/png" href="/favicon-light.png" media="(prefers-color-scheme:no-preference)">

C’est aussi simple que ça.

Pour générer des favicons, vous avez l’embarras du choix :

Favicon SVG natif

Le format SVG est supporté comme favicon par tous les navigateurs modernes. Il offre l’avantage d’être vectoriel (net sur tous les écrans) et de supporter les media queries directement :

<link rel="icon" type="image/svg+xml" href="/favicon.svg">

SVG avec prefers-color-scheme intégré

On peut intégrer la gestion clair/sombre directement dans le fichier SVG, sans avoir besoin de deux fichiers ni de JavaScript :

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
.bg { fill: #ffffff; }
.fg { fill: #000000; }
@media (prefers-color-scheme: dark) {
.bg { fill: #1a1a2e; }
.fg { fill: #e0e0e0; }
}
</style>
<rect class="bg" width="32" height="32" rx="6"/>
<text class="fg" x="16" y="22" font-size="18" text-anchor="middle" font-family="sans-serif">B</text>
</svg>

Un seul fichier, pas de balise media dans le HTML, tout est géré dans le SVG. C’est la solution la plus propre.

<!-- Un seul link suffit -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg">

Changement dynamique par JavaScript

Si vous avez besoin de changer le favicon en fonction d’un bouton utilisateur (pas seulement du thème système) :

function setFavicon(theme) {
const link = document.querySelector("link[rel~='icon']");
if (link) {
link.href = theme === 'dark'
? '/favicon-dark.png'
: '/favicon-light.png';
}
}

// Basculer avec un bouton
document.getElementById('themeToggle').addEventListener('click', () => {
const isDark = document.body.classList.toggle('dark-theme');
setFavicon(isDark ? 'dark' : 'light');
});

Pour détecter le changement de thème système en temps réel :

window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
setFavicon(e.matches ? 'dark' : 'light');
});

Génération programmatique avec Sharp

Pour ceux qui veulent automatiser la génération de leurs favicons (taille, couleurs, thèmes) :

const sharp = require('sharp');
const fs = require('fs');

// Générer un favicon light et dark
async function genererFavicons(texte) {
const largeur = 32, hauteur = 32;

for (const [theme, bg, fg] of [
['light', '#ffffff', '#000000'],
['dark', '#1a1a2e', '#e0e0e0']
]) {
const svg = `
<svg width="${largeur}" height="${hauteur}">
<rect width="${largeur}" height="${hauteur}" rx="6" fill="${bg}"/>
<text x="16" y="22" font-size="18"
text-anchor="middle" fill="${fg}"
font-family="sans-serif">${texte}</text>
</svg>`;

await sharp(Buffer.from(svg))
.resize(largeur, hauteur)
.png()
.toFile(`favicon-${theme}.png`);
}

// Générer aussi l'ico 32x32
await sharp(Buffer.from(`
<svg width="32" height="32">
<rect width="32" height="32" rx="6" fill="#ffffff"/>
<text x="16" y="22" font-size="18"
text-anchor="middle" fill="#000000"
font-family="sans-serif">${texte}</text>
</svg>`))
.resize(32, 32)
.toFile('favicon.ico');
}

genererFavicons('M').catch(console.error);

Sharp permet aussi de générer les icônes pour iOS (apple-touch-icon), Android (manifest), etc. Le tout automatisé dans votre build.