Slik lager du adaptive favicons for lys og mørk modus: Komplett utviklerguide

Favicon.im

Moderne nettsteder må tilpasse seg brukerpreferanser, og favicon-tematisering er en ofte oversett detalj som kan forbedre brukeropplevelsen betydelig. Når brukere bytter mellom lys og mørk modus, bør faviconet ditt tilpasse seg tilsvarende for å opprettholde visuell konsistens.

Denne omfattende guiden dekker alt fra enkle kun-HTML-løsninger til avanserte JavaScript-implementeringer på tvers av populære rammeverk. Enten du bygger et statisk nettsted eller en kompleks webapplikasjon, finner du riktig tilnærming for prosjektet ditt.

Metode 1: Kun-HTML-løsning (anbefalt for de fleste nettsteder)

Kun-HTML-tilnærmingen er den mest pålitelige metoden og krever ingen JavaScript. Den bruker CSS-mediespørringer i media-attributtet til favicon-link-tagger for automatisk å bytte favicons basert på brukerens systempreferanse.

Hvorfor denne metoden fungerer best:

  • Null JavaScript nødvendig
  • Fungerer umiddelbart ved sidelasting
  • Støttet av alle moderne nettlesere
  • Ingen ytelsesoverhead

Grunnleggende implementering

<head>
  <!-- Standard favicon (reserveløsning for nettlesere uten støtte) -->
  <link rel="icon" href="/favicon-light.ico" type="image/x-icon">

  <!-- Favicon for lys modus -->
  <link rel="icon" href="/favicon-light.ico" type="image/x-icon" media="(prefers-color-scheme: light)">

  <!-- Favicon for mørk modus -->
  <link rel="icon" href="/favicon-dark.ico" type="image/x-icon" media="(prefers-color-scheme: dark)">
</head>

Komplett flerstegs-implementering

For omfattende enhetsstøtte, implementer flere størrelser med temavarianter:

<head>
  <!-- Standard favicons (reserveløsning) -->
  <link rel="icon" type="image/x-icon" href="/favicon-light.ico">
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-light-32x32.png">

  <!-- Favicons for lys modus -->
  <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)">

  <!-- Favicons for mørk modus -->
  <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 med innebygd CSS -->
  <link rel="icon" type="image/svg+xml" href="/favicon-adaptive.svg">
</head>

Adaptivt SVG-favicon

Lag et enkelt SVG-favicon som automatisk tilpasser seg fargeskjemaet:

<!-- 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>

  <!-- Design for lys modus -->
  <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>

  <!-- Design for mørk modus -->
  <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>

Metode 2: JavaScript-implementering

Når du trenger dynamisk favicon-bytte utover systempreferanser — som tilpassede temakontroller eller sanntidsoppdateringer — gir JavaScript den fleksibiliteten du trenger.

Bruk JavaScript når:

  • Du har tilpassede temakontroller
  • Du trenger å synkronisere med appens tematilstand
  • Du vil oppdatere favicons uten sideinnlasting
  • Du bygger en enkeltside-applikasjon

Grunnleggende JavaScript-tilnærming

// Funksjon for å oppdatere favicon basert på tema
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);
  }
}

// Lytt etter systemtemaendringer
if (window.matchMedia) {
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

  // Sett initialt favicon
  updateFavicon(mediaQuery.matches ? 'dark' : 'light');

  // Lytt etter endringer
  mediaQuery.addEventListener('change', (e) => {
    updateFavicon(e.matches ? 'dark' : 'light');
  });
}

Avansert JavaScript med flere størrelser

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() {
    // Sett initialt tema
    this.updateTheme(this.getSystemTheme());

    // Lytt etter systemendringer
    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`;
    });

    // Oppdater standard ico-fil
    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`;
  }

  // Metode for å manuelt sette tema (for tilpassede temakontroller)
  setTheme(theme) {
    this.updateTheme(theme);
  }
}

// Initialiser
const faviconManager = new FaviconManager();

// Eksporter for manuell temabytte
window.faviconManager = faviconManager;

Metode 3: Rammeverksintegrasjon

Moderne rammeverk tilbyr elegante måter å håndtere favicon-tematisering. Slik implementerer du adaptive favicons i de mest populære JavaScript-rammeverkene.

React-implementering

import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';

function AdaptiveFavicon() {
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    // Sjekk systempreferanse
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    setTheme(mediaQuery.matches ? 'dark' : 'light');

    // Lytt etter endringer
    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-implementering

<template>
  <div>
    <!-- Ditt appinnhold -->
  </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(() => {
  // Sjekk systempreferanse
  if (window.matchMedia) {
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
    isDark.value = mediaQuery.matches

    // Lytt etter endringer
    mediaQuery.addEventListener('change', (e) => {
      isDark.value = e.matches
    })
  }

  updateFavicon()
})

watch(isDark, updateFavicon)
</script>

Nuxt 3-implementering

// 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));
              }
            })();
          `
        }
      ]
    }
  }
})

Metode 4: CSS-i-JS favicon (avansert)

Generer favicons dynamisk ved hjelp av Canvas og CSS-farger:

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];

    // Tøm canvas
    this.ctx.clearRect(0, 0, 32, 32);

    // Tegn bakgrunn
    this.ctx.fillStyle = bg;
    this.ctx.fillRect(0, 0, 32, 32);

    // Tegn kant
    this.ctx.strokeStyle = text;
    this.ctx.lineWidth = 2;
    this.ctx.strokeRect(2, 2, 28, 28);

    // Tegn ikon (eksempel: bokstav eller symbol)
    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;
  }
}

// Bruk
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');
});

Beste praksis for design

Å lage effektive adaptive favicons krever nøye oppmerksomhet for designprinsipper og brukeropplevelse.

Fargekontrast og synlighet

Favicon-design for lys modus:

  • Bruk mørke elementer (tekst, ikoner) på gjennomsiktige eller lyse bakgrunner
  • Sikt mot WCAG AA kontrastforhold (minimum 4,5:1)
  • Test utseende på hvite nettleserfaner og bokmerkelinjer
  • Sørg for tydelighet ved 16x16 piksler (minste vanlige størrelse)

Favicon-design for mørk modus:

  • Bruk lyse elementer på gjennomsiktige eller mørke bakgrunner
  • Test synlighet mot mørke nettlesertemaer
  • Unngå ren hvit (#ffffff) — bruk off-white (#f0f0f0) for bedre balanse
  • Vurder subtile skygger eller omriss for definisjon

Tips for designkonsistens

  1. Oppretthold merkevaregjenkjenning — Hold kjernedesignelementene konsistente
  2. Test i flere størrelser — 16x16, 32x32 og 180x180 piksler
  3. Bruk enkle former — Komplekse detaljer forsvinner i små størrelser
  4. Ta hensyn til fargeblinde brukere — Ikke stol utelukkende på farge for differensiering

Navnekonvensjon for filer

Organiser favicon-filene dine med tydelig navngivning:

/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

Nettleserkompatibilitet

Moderne nettleserstøtte for adaptive favicons

Nettleser Mediespørring-støtte Merknader
Chrome 76+ Full støtte Fungerer perfekt
Firefox 67+ Full støtte Utmerket implementering
Safari 12.1+ Full støtte iOS Safari inkludert
Edge 79+ Full støtte Chromium-basert Edge
Internet Explorer Ingen støtte Bruk JavaScript-reserveløsning

Markedsdekning: Disse versjonene dekker ~95 % av global nettleserbruk per 2025.

Reserveløsningsstrategi

<!-- Tilby alltid reserveløsninger -->
<link rel="icon" href="/favicon-light.ico" type="image/x-icon">

<!-- Forbedret støtte for moderne nettlesere -->
<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-reserveløsning for eldre nettlesere -->
<script>
  if (!window.matchMedia || !CSS.supports('(prefers-color-scheme: dark)')) {
    // Last favicon basert på tid på døgnet eller andre heuristikker
    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>

Testing og validering

Manuell testsjekkliste

  • [ ] Test i lys modus (systempreferanse)
  • [ ] Test i mørk modus (systempreferanse)
  • [ ] Verifiser at favicon endres umiddelbart ved systemtemabytte
  • [ ] Sjekk ulike nettlesere (Chrome, Firefox, Safari, Edge)
  • [ ] Test på mobile enheter
  • [ ] Valider reserveoppførsel i eldre nettlesere

Automatisert testing

// Testskript for favicon-temabytte
function testFaviconThemes() {
  const tests = [
    { theme: 'light', expected: '/favicon-light.ico' },
    { theme: 'dark', expected: '/favicon-dark.ico' }
  ];

  tests.forEach(({ theme, expected }) => {
    // Mock mediespørring
    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(),
      })),
    });

    // Utløs oppdatering
    updateFavicon(theme);

    // Bekreft
    const favicon = document.querySelector('link[rel="icon"]');
    expect(favicon.href).toContain(expected);
  });
}

Ytelsesoptimalisering

Forhåndslast temafavicons

<!-- Forhåndslast begge temafavicons for umiddelbar bytte -->
<link rel="preload" as="image" href="/favicon-light.ico">
<link rel="preload" as="image" href="/favicon-dark.ico">

Minimer filstørrelser

  • Hold ICO-filer under 1KB
  • Optimaliser PNG-filer med verktøy som TinyPNG
  • Bruk SVG for enkle geometriske design
  • Vurder WebP-format for moderne nettlesere

Bufringsstrategi

# Nginx-konfigurasjon for favicon-bufring
location ~* \.(ico|png|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    add_header Vary "Accept-Encoding";
}

Feilsøking av vanlige problemer

Favicon bytter ikke mellom temaer

Symptomer: Favicon forblir det samme uavhengig av systemtemaendringer

Vanlige årsaker og løsninger:

  1. Nettleserbufferproblemer

    <!-- Legg til cache-busting-parametere -->
    <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)">
    
  2. Feil mediespørringssyntaks

    <!-- ❌ Feil -->
    <link rel="icon" href="/favicon-dark.ico" media="dark">
    
    <!-- ✅ Riktig -->
    <link rel="icon" href="/favicon-dark.ico" media="(prefers-color-scheme: dark)">
    

Flere favicons lastes samtidig

Symptomer: Nettverksfanen viser flere favicon-forespørsler

Løsning: Bruk JavaScript for å erstatte i stedet for å legge til:

function replaceFavicon(href) {
  // Fjern alle eksisterende favicon-lenker
  document.querySelectorAll('link[rel*="icon"]').forEach(link => link.remove());

  // Legg til nytt favicon
  const link = document.createElement('link');
  link.rel = 'icon';
  link.type = 'image/x-icon';
  link.href = href;
  document.head.appendChild(link);
}

SVG-favicons vises ikke

Symptomer: SVG-favicon fungerer i noen nettlesere men ikke andre

Rotårsak: Begrenset SVG-favicon-støtte i eldre nettlesere

Løsning: Tilby alltid PNG-reserveløsninger:

<!-- Moderne nettlesere: SVG med mediespørringer -->
<link rel="icon" type="image/svg+xml" href="/favicon-adaptive.svg">

<!-- Reserveløsning: PNG for eldre nettlesere -->
<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)">

Avanserte teknikker

Temabevisste varslingsmerker

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) {
          // Tegn varslingsmerke
          const badgeSize = 12;
          const x = 32 - badgeSize;
          const y = 0;

          // Merkebakgrunn
          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();

          // Merketekst
          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;
  }
}

// Bruk
const notificationFavicon = new NotificationFavicon();
notificationFavicon.updateWithNotification(3); // Vis merke med antall 3

Oppsummering og neste steg

Adaptive favicons representerer en liten, men virkningsfull måte å forbedre brukeropplevelsen på. De viser oppmerksomhet for detaljer og respekt for brukerpreferanser, og bidrar til et mer polert og profesjonelt nettsted.

Velg riktig metode for prosjektet ditt

Metode Best for Kompleksitet Ytelse
Kun HTML Statiske nettsteder, blogger, markedsføringssider Lav Utmerket
JavaScript SPAer, tilpassede temaer, dynamiske oppdateringer Middels God
Rammeverksintegrasjon React/Vue/Nuxt-applikasjoner Middels God
Avanserte teknikker Varslingssystemer, sanntidsoppdateringer Høy Variabel

Implementeringssjekkliste

Før du distribuerer ditt adaptive favicon-system:

  • [ ] Lag både lyse og mørke favicon-versjoner
  • [ ] Test i flere nettlesere (Chrome, Firefox, Safari, Edge)
  • [ ] Verifiser at bytte fungerer med systemtemaendringer
  • [ ] Test på mobile enheter (iOS Safari, Android Chrome)
  • [ ] Optimaliser filstørrelser (hold under 1KB for ICO-filer)
  • [ ] Legg til passende reserveløsninger for eldre nettlesere
  • [ ] Valider implementeringen med verktøy som Favicon.im

Ytelseseffekt

Riktig implementert har adaptive favicons minimal ytelseseffekt:

  • Kun-HTML-metode: Null JavaScript-overhead
  • Filstørrelseseffekt: ~2-4KB totalt (lys + mørk versjon)
  • Lastetid: Ubetydelig med riktig bufring

Gå videre

Vurder disse avanserte optimaliseringene:

  • Forhåndslast kritiske favicon-ressurser for umiddelbar bytte
  • Bruk WebP-format for moderne nettlesere (med PNG-reserveløsninger)
  • Implementer dynamiske favicon-merker for varsler
  • Legg til favicon-animasjoner for spesielle hendelser eller statuser

Ved å implementere adaptive favicons gjennomtenkt, skaper du en mer sammenhengende og brukervennlig nettopplevelse som tilpasser seg moderne brukerpreferanser.

Check Your Favicon

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.

15M+
Monthly Favicon Requests
100%
Free Forever