Hoe maak je adaptieve favicons voor lichte en donkere modus: Complete ontwikkelaarshandleiding
Moderne websites moeten zich aanpassen aan gebruikersvoorkeuren, en favicon-theming is een vaak over het hoofd gezien detail dat de gebruikerservaring aanzienlijk kan verbeteren. Wanneer gebruikers wisselen tussen lichte en donkere modus, moet je favicon zich dienovereenkomstig aanpassen om visuele consistentie te behouden.
Deze uitgebreide handleiding behandelt alles van eenvoudige HTML-only oplossingen tot geavanceerde JavaScript-implementaties in populaire frameworks. Of je nu een statische site of een complexe webapplicatie bouwt, je vindt hier de juiste aanpak voor je project.
Methode 1: HTML-only oplossing (Aanbevolen voor de meeste sites)
De HTML-only aanpak is de meest betrouwbare methode en vereist geen JavaScript. Het gebruikt CSS media queries binnen het media-attribuut van favicon link-tags om automatisch favicons te wisselen op basis van de systeemvoorkeur van de gebruiker.
Waarom deze methode het beste werkt:
- Nul JavaScript vereist
- Werkt direct bij het laden van de pagina
- Ondersteund door alle moderne browsers
- Geen prestatie-overhead
Basisimplementatie
<head>
<!-- Standaard favicon (fallback voor niet-ondersteunde browsers) -->
<link rel="icon" href="/favicon-light.ico" type="image/x-icon">
<!-- Lichte modus-favicon -->
<link rel="icon" href="/favicon-light.ico" type="image/x-icon" media="(prefers-color-scheme: light)">
<!-- Donkere modus-favicon -->
<link rel="icon" href="/favicon-dark.ico" type="image/x-icon" media="(prefers-color-scheme: dark)">
</head>
Volledige multi-maat implementatie
Voor uitgebreide apparaatondersteuning, implementeer meerdere maten met thema-varianten:
<head>
<!-- Standaard favicons (fallback) -->
<link rel="icon" type="image/x-icon" href="/favicon-light.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-light-32x32.png">
<!-- Lichte modus-favicons -->
<link rel="icon" type="image/x-icon" href="/favicon-light.ico" media="(prefers-color-scheme: light)">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-light-16x16.png" media="(prefers-color-scheme: light)">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-light-32x32.png" media="(prefers-color-scheme: light)">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-light.png" media="(prefers-color-scheme: light)">
<!-- Donkere modus-favicons -->
<link rel="icon" type="image/x-icon" href="/favicon-dark.ico" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-dark-16x16.png" media="(prefers-color-scheme: dark)">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-dark-32x32.png" media="(prefers-color-scheme: dark)">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-dark.png" media="(prefers-color-scheme: dark)">
<!-- SVG-favicons met ingebedde CSS -->
<link rel="icon" type="image/svg+xml" href="/favicon-adaptive.svg">
</head>
Adaptieve SVG-favicon
Maak een enkele SVG-favicon die zich automatisch aanpast aan het kleurenschema:
<!-- favicon-adaptive.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
.light-mode { fill: #000000; }
.dark-mode { fill: #ffffff; }
@media (prefers-color-scheme: dark) {
.light-mode { display: none; }
}
@media (prefers-color-scheme: light) {
.dark-mode { display: none; }
}
</style>
<!-- Lichte modus-ontwerp -->
<circle class="light-mode" cx="16" cy="16" r="12"/>
<text class="light-mode" x="16" y="20" text-anchor="middle" fill="#fff" font-size="14">L</text>
<!-- Donkere modus-ontwerp -->
<circle class="dark-mode" cx="16" cy="16" r="12"/>
<text class="dark-mode" x="16" y="20" text-anchor="middle" fill="#000" font-size="14">D</text>
</svg>
Methode 2: JavaScript-implementatie
Wanneer je dynamisch favicon wisselen nodig hebt voorbij systeemvoorkeuren — zoals aangepaste thema-schakelaars of realtime updates — biedt JavaScript de flexibiliteit die je nodig hebt.
Gebruik JavaScript wanneer:
- Je aangepaste themabesturingen hebt
- Je wilt synchroniseren met de themastatus van je app
- Je favicons wilt bijwerken zonder paginaverversing
- Je een single-page applicatie bouwt
Basis JavaScript-aanpak
// Functie om favicon bij te werken op basis van thema
function updateFavicon(theme) {
const favicon = document.querySelector('link[rel="icon"]') ||
document.createElement('link');
favicon.rel = 'icon';
favicon.type = 'image/png';
favicon.href = theme === 'dark' ? '/favicon-dark.png' : '/favicon-light.png';
if (!document.querySelector('link[rel="icon"]')) {
document.head.appendChild(favicon);
}
}
// Luister naar systeemthemawijzigingen
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
// Stel initieel favicon in
updateFavicon(mediaQuery.matches ? 'dark' : 'light');
// Luister naar wijzigingen
mediaQuery.addEventListener('change', (e) => {
updateFavicon(e.matches ? 'dark' : 'light');
});
}
Geavanceerde JavaScript met meerdere maten
class FaviconManager {
constructor() {
this.sizes = [
{ size: '16x16', selector: 'link[rel="icon"][sizes="16x16"]' },
{ size: '32x32', selector: 'link[rel="icon"][sizes="32x32"]' },
{ size: '180x180', selector: 'link[rel="apple-touch-icon"]' }
];
this.init();
}
init() {
// Stel initieel thema in
this.updateTheme(this.getSystemTheme());
// Luister naar systeemwijzigingen
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
this.updateTheme(e.matches ? 'dark' : 'light');
});
}
}
getSystemTheme() {
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light';
}
updateTheme(theme) {
this.sizes.forEach(({ size, selector }) => {
let link = document.querySelector(selector);
if (!link) {
link = document.createElement('link');
link.rel = size === '180x180' ? 'apple-touch-icon' : 'icon';
link.type = 'image/png';
if (size !== '180x180') link.sizes = size;
document.head.appendChild(link);
}
link.href = `/favicon-${theme}-${size}.png`;
});
// Update standaard ico-bestand
let icoLink = document.querySelector('link[rel="icon"][type="image/x-icon"]');
if (!icoLink) {
icoLink = document.createElement('link');
icoLink.rel = 'icon';
icoLink.type = 'image/x-icon';
document.head.appendChild(icoLink);
}
icoLink.href = `/favicon-${theme}.ico`;
}
// Methode om handmatig thema in te stellen (voor aangepaste thema-schakelaars)
setTheme(theme) {
this.updateTheme(theme);
}
}
// Initialiseren
const faviconManager = new FaviconManager();
// Exporteren voor handmatig thema wisselen
window.faviconManager = faviconManager;
Methode 3: Framework-integratie
Moderne frameworks bieden elegante manieren om favicon-theming af te handelen. Zo implementeer je adaptieve favicons in de meest populaire JavaScript-frameworks.
React-implementatie
import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
function AdaptiveFavicon() {
const [theme, setTheme] = useState('light');
useEffect(() => {
// Controleer systeemvoorkeur
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
setTheme(mediaQuery.matches ? 'dark' : 'light');
// Luister naar wijzigingen
const handleChange = (e) => {
setTheme(e.matches ? 'dark' : 'light');
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);
return (
<Helmet>
<link rel="icon" type="image/x-icon" href={`/favicon-${theme}.ico`} />
<link rel="icon" type="image/png" sizes="32x32" href={`/favicon-${theme}-32x32.png`} />
<link rel="apple-touch-icon" sizes="180x180" href={`/apple-touch-icon-${theme}.png`} />
</Helmet>
);
}
Vue 3-implementatie
<template>
<div>
<!-- Je app-content -->
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useHead } from '@unhead/vue'
const isDark = ref(false)
const updateFavicon = () => {
const theme = isDark.value ? 'dark' : 'light'
useHead({
link: [
{ rel: 'icon', type: 'image/x-icon', href: `/favicon-${theme}.ico` },
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: `/favicon-${theme}-32x32.png` },
{ rel: 'apple-touch-icon', sizes: '180x180', href: `/apple-touch-icon-${theme}.png` }
]
})
}
onMounted(() => {
// Controleer systeemvoorkeur
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
isDark.value = mediaQuery.matches
// Luister naar wijzigingen
mediaQuery.addEventListener('change', (e) => {
isDark.value = e.matches
})
}
updateFavicon()
})
watch(isDark, updateFavicon)
</script>
Nuxt 3-implementatie
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{
innerHTML: `
(function() {
const updateFavicon = (isDark) => {
const theme = isDark ? 'dark' : 'light';
const links = [
{ rel: 'icon', type: 'image/x-icon', href: \`/favicon-\${theme}.ico\` },
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: \`/favicon-\${theme}-32x32.png\` }
];
links.forEach(linkData => {
let link = document.querySelector(\`link[rel="\${linkData.rel}"][sizes="\${linkData.sizes || 'any'}"]\`);
if (!link) {
link = document.createElement('link');
Object.assign(link, linkData);
document.head.appendChild(link);
} else {
link.href = linkData.href;
}
});
};
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
updateFavicon(mediaQuery.matches);
mediaQuery.addEventListener('change', e => updateFavicon(e.matches));
}
})();
`
}
]
}
}
})
Methode 4: CSS-in-JS favicon (Geavanceerd)
Genereer favicons dynamisch met Canvas en CSS-kleuren:
class DynamicFaviconGenerator {
constructor() {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.canvas.width = 32;
this.canvas.height = 32;
}
generateFavicon(theme) {
const colors = {
light: { bg: '#ffffff', text: '#000000' },
dark: { bg: '#000000', text: '#ffffff' }
};
const { bg, text } = colors[theme];
// Wis canvas
this.ctx.clearRect(0, 0, 32, 32);
// Teken achtergrond
this.ctx.fillStyle = bg;
this.ctx.fillRect(0, 0, 32, 32);
// Teken rand
this.ctx.strokeStyle = text;
this.ctx.lineWidth = 2;
this.ctx.strokeRect(2, 2, 28, 28);
// Teken icoon (voorbeeld: letter of symbool)
this.ctx.fillStyle = text;
this.ctx.font = 'bold 20px Arial';
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText('🌙', 16, 16);
return this.canvas.toDataURL('image/png');
}
updateFavicon(theme) {
const dataUrl = this.generateFavicon(theme);
let link = document.querySelector('link[rel="icon"]');
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
link.type = 'image/png';
document.head.appendChild(link);
}
link.href = dataUrl;
}
}
// Gebruik
const generator = new DynamicFaviconGenerator();
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
generator.updateFavicon(mediaQuery.matches ? 'dark' : 'light');
mediaQuery.addEventListener('change', e => {
generator.updateFavicon(e.matches ? 'dark' : 'light');
});
Ontwerp best practices
Het maken van effectieve adaptieve favicons vereist zorgvuldige aandacht voor ontwerpprincipes en gebruikerservaring.
Kleurcontrast en zichtbaarheid
Favicon-ontwerp voor lichte modus:
- Gebruik donkere elementen (tekst, iconen) op transparante of lichte achtergronden
- Streef naar WCAG AA-contrastverhoudingen (minimaal 4.5:1)
- Test het uiterlijk op witte browsertabs en bladwijzerbalken
- Zorg voor helderheid op 16x16 pixels (kleinste gangbare maat)
Favicon-ontwerp voor donkere modus:
- Gebruik lichte elementen op transparante of donkere achtergronden
- Test zichtbaarheid tegen donkere browserthema's
- Vermijd puur wit (#ffffff) - gebruik gebroken wit (#f0f0f0) voor betere balans
- Overweeg subtiele schaduwen of contouren voor definitie
Tips voor ontwerpconsistentie
- Behoud merkherkenning - Houd je kern-ontwerpelementen consistent
- Test op meerdere maten - 16x16, 32x32 en 180x180 pixels
- Gebruik eenvoudige vormen - Complexe details verdwijnen op kleine formaten
- Houd rekening met kleurenblinde gebruikers - Vertrouw niet alleen op kleur voor differentiatie
Bestandsnaamconventie
Organiseer je faviconbestanden met duidelijke naamgeving:
/public/
├── favicon-light.ico
├── favicon-dark.ico
├── favicon-light-16x16.png
├── favicon-dark-16x16.png
├── favicon-light-32x32.png
├── favicon-dark-32x32.png
├── apple-touch-icon-light.png
├── apple-touch-icon-dark.png
└── favicon-adaptive.svg
Browsercompatibiliteit
Moderne browserondersteuning voor adaptieve favicons
| Browser | Media query-ondersteuning | Opmerkingen |
|---|---|---|
| Chrome 76+ | Volledige ondersteuning | Werkt perfect |
| Firefox 67+ | Volledige ondersteuning | Uitstekende implementatie |
| Safari 12.1+ | Volledige ondersteuning | Inclusief iOS Safari |
| Edge 79+ | Volledige ondersteuning | Chromium-gebaseerde Edge |
| Internet Explorer | Geen ondersteuning | Gebruik JavaScript-fallback |
Marktdekking: Deze versies dekken ~95% van het wereldwijde browsergebruik in 2025.
Fallback-strategie
<!-- Bied altijd fallbacks -->
<link rel="icon" href="/favicon-light.ico" type="image/x-icon">
<!-- Verbeterde ondersteuning voor moderne browsers -->
<link rel="icon" href="/favicon-light.ico" type="image/x-icon" media="(prefers-color-scheme: light)">
<link rel="icon" href="/favicon-dark.ico" type="image/x-icon" media="(prefers-color-scheme: dark)">
<!-- JavaScript-fallback voor oudere browsers -->
<script>
if (!window.matchMedia || !CSS.supports('(prefers-color-scheme: dark)')) {
// Laad favicon op basis van tijdstip of andere heuristieken
const hour = new Date().getHours();
const isDark = hour < 6 || hour > 18;
document.querySelector('link[rel="icon"]').href =
isDark ? '/favicon-dark.ico' : '/favicon-light.ico';
}
</script>
Testen en validatie
Handmatige testchecklist
- [ ] Test in lichte modus (systeemvoorkeur)
- [ ] Test in donkere modus (systeemvoorkeur)
- [ ] Controleer of favicon direct verandert bij systeemthema-wisseling
- [ ] Controleer in verschillende browsers (Chrome, Firefox, Safari, Edge)
- [ ] Test op mobiele apparaten
- [ ] Valideer fallback-gedrag in oudere browsers
Geautomatiseerd testen
// Testscript voor favicon-themawisseling
function testFaviconThemes() {
const tests = [
{ theme: 'light', expected: '/favicon-light.ico' },
{ theme: 'dark', expected: '/favicon-dark.ico' }
];
tests.forEach(({ theme, expected }) => {
// Mock media query
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: query.includes('dark') ? theme === 'dark' : theme === 'light',
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
})),
});
// Trigger update
updateFavicon(theme);
// Assert
const favicon = document.querySelector('link[rel="icon"]');
expect(favicon.href).toContain(expected);
});
}
Prestatieoptimalisatie
Preload thema-favicons
<!-- Preload beide thema-favicons voor directe wisseling -->
<link rel="preload" as="image" href="/favicon-light.ico">
<link rel="preload" as="image" href="/favicon-dark.ico">
Minimaliseer bestandsgrootten
- Houd ICO-bestanden onder 1KB
- Optimaliseer PNG-bestanden met tools zoals TinyPNG
- Gebruik SVG voor eenvoudige geometrische ontwerpen
- Overweeg WebP-formaat voor moderne browsers
Cachingstrategie
# Nginx-configuratie voor favicon-caching
location ~* \.(ico|png|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}
Probleemoplossing veelvoorkomende problemen
Favicon wisselt niet tussen thema's
Symptomen: Favicon blijft hetzelfde ongeacht systeemthemawijzigingen
Veelvoorkomende oorzaken & oplossingen:
-
Browsercache-problemen
<!-- Voeg cache-busting parameters toe --> <link rel="icon" href="/favicon-light.ico?v=2025" media="(prefers-color-scheme: light)"> <link rel="icon" href="/favicon-dark.ico?v=2025" media="(prefers-color-scheme: dark)"> -
Onjuiste media query-syntax
<!-- Fout --> <link rel="icon" href="/favicon-dark.ico" media="dark"> <!-- Correct --> <link rel="icon" href="/favicon-dark.ico" media="(prefers-color-scheme: dark)">
Meerdere favicons laden tegelijk
Symptomen: Network-tab toont meerdere favicon-verzoeken
Oplossing: Gebruik JavaScript om te vervangen in plaats van toe te voegen:
function replaceFavicon(href) {
// Verwijder alle bestaande favicon-links
document.querySelectorAll('link[rel*="icon"]').forEach(link => link.remove());
// Voeg nieuwe favicon toe
const link = document.createElement('link');
link.rel = 'icon';
link.type = 'image/x-icon';
link.href = href;
document.head.appendChild(link);
}
SVG-favicons worden niet weergegeven
Symptomen: SVG-favicon werkt in sommige browsers maar niet in andere
Oorzaak: Beperkte SVG-favicon-ondersteuning in oudere browsers
Oplossing: Bied altijd PNG-fallbacks:
<!-- Moderne browsers: SVG met media queries -->
<link rel="icon" type="image/svg+xml" href="/favicon-adaptive.svg">
<!-- Fallback: PNG voor oudere browsers -->
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-light-32x32.png" media="(prefers-color-scheme: light)">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-dark-32x32.png" media="(prefers-color-scheme: dark)">
Geavanceerde technieken
Themabewuste notificatiebadges
class NotificationFavicon {
constructor() {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.canvas.width = 32;
this.canvas.height = 32;
this.baseIcons = {
light: '/favicon-light-32x32.png',
dark: '/favicon-dark-32x32.png'
};
}
async drawWithBadge(theme, count) {
const baseIcon = new Image();
baseIcon.src = this.baseIcons[theme];
return new Promise(resolve => {
baseIcon.onload = () => {
this.ctx.clearRect(0, 0, 32, 32);
this.ctx.drawImage(baseIcon, 0, 0, 32, 32);
if (count > 0) {
// Teken notificatiebadge
const badgeSize = 12;
const x = 32 - badgeSize;
const y = 0;
// Badge-achtergrond
this.ctx.fillStyle = '#ff4444';
this.ctx.beginPath();
this.ctx.arc(x + badgeSize/2, y + badgeSize/2, badgeSize/2, 0, 2 * Math.PI);
this.ctx.fill();
// Badge-tekst
this.ctx.fillStyle = 'white';
this.ctx.font = '8px Arial';
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText(
count > 9 ? '9+' : count.toString(),
x + badgeSize/2,
y + badgeSize/2
);
}
resolve(this.canvas.toDataURL());
};
});
}
async updateWithNotification(count = 0) {
const theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const dataUrl = await this.drawWithBadge(theme, count);
let link = document.querySelector('link[rel="icon"]');
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.head.appendChild(link);
}
link.href = dataUrl;
}
}
// Gebruik
const notificationFavicon = new NotificationFavicon();
notificationFavicon.updateWithNotification(3); // Toon badge met aantal 3
Samenvatting en volgende stappen
Adaptieve favicons zijn een kleine maar impactvolle manier om de gebruikerservaring te verbeteren. Ze tonen aandacht voor detail en respect voor gebruikersvoorkeuren, en dragen bij aan een meer gepolijste en professionele website.
Kies de juiste methode voor je project
| Methode | Beste voor | Complexiteit | Prestaties |
|---|---|---|---|
| HTML-only | Statische sites, blogs, marketingpagina's | Laag | Uitstekend |
| JavaScript | SPA's, aangepaste thema's, dynamische updates | Gemiddeld | Goed |
| Framework-integratie | React/Vue/Nuxt-applicaties | Gemiddeld | Goed |
| Geavanceerde technieken | Notificatiesystemen, realtime updates | Hoog | Variabel |
Implementatiechecklist
Voordat je je adaptieve faviconsysteem deployt:
- [ ] Maak zowel lichte als donkere faviconversies
- [ ] Test in meerdere browsers (Chrome, Firefox, Safari, Edge)
- [ ] Controleer of wisselen werkt bij systeemthemawijzigingen
- [ ] Test op mobiele apparaten (iOS Safari, Android Chrome)
- [ ] Optimaliseer bestandsgrootten (houd ICO-bestanden onder 1KB)
- [ ] Voeg geschikte fallbacks toe voor oudere browsers
- [ ] Valideer implementatie met tools zoals Favicon.im
Prestatie-impact
Bij correcte implementatie hebben adaptieve favicons minimale prestatie-impact:
- HTML-only methode: Nul JavaScript-overhead
- Bestandsgrootte-impact: ~2-4KB totaal (lichte + donkere versie)
- Laadtijd: Verwaarloosbaar bij juiste caching
Verdere stappen
Overweeg deze geavanceerde optimalisaties:
- Preload essentiële favicon-assets voor directe wisseling
- Gebruik WebP-formaat voor moderne browsers (met PNG-fallbacks)
- Implementeer dynamische favicon-badges voor notificaties
- Voeg favicon-animaties toe voor speciale evenementen of statussen
Door adaptieve favicons doordacht te implementeren, creëer je een meer samenhangende en gebruiksvriendelijke webervaring die zich aanpast aan moderne gebruikersvoorkeuren.
Use favicon.im to quickly check if your favicon is configured correctly. Our free tool ensures your website's favicon displays properly across all browsers and devices.
Free Public Service
Favicon.im is a completely free public service trusted by developers worldwide.