Building an Adaptive Favicon
How to create a single SVG favicon that automatically switches between light and dark mode — no JavaScript required.
Overview
A favicon is the small icon that appears in browser tabs, bookmarks, history, and link
previews. Traditionally a 16x16 .ico file, it is often treated as an afterthought — a single static image that looks fine on a white
background and becomes nearly invisible when the browser switches to dark mode.
Modern browsers support SVG favicons. Because SVG is
a text-based vector format it can embed CSS, including @media queries. This means you can ship a single favicon.svg that responds to prefers-color-scheme, showing a light-mode icon to users with light system themes and a dark-mode icon to
everyone else — exactly like your website's own UI does.
The result is a favicon that feels polished and intentional on every device: in browser tabs, pinned tabs, reader apps, bookmark bars, and link previews.
.ico is still essential.How It Works
The key is a prefers-color-scheme CSS media query embedded directly inside the SVG's <style> block. The browser evaluates the media query the same way it does in a stylesheet, so the icon
updates instantly whenever the user changes their system theme — no page reload, no JavaScript.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
#light { display: block; }
#dark { display: none; }
@media (prefers-color-scheme: dark) {
#light { display: none; }
#dark { display: block; }
}
</style>
<!-- shown in light mode -->
<g id="light"> … </g>
<!-- shown in dark mode -->
<g id="dark"> … </g>
</svg> When the OS is in light mode the #dark group is hidden. When it switches to dark mode the media query fires, hiding #light and revealing #dark.
Pure CSS — zero dependencies.
Step 1 — Create favicon.svg
Create a new file called favicon.svg at the root of your project, next to your existing favicon.ico. The outer <svg> tag is the equivalent of the <html> tag for your icon. Give it a square viewBox — favicons
are always square:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
</svg> You need two versions of your artwork — one designed for light backgrounds and one for dark backgrounds. If your icon comes from a design tool like Figma or Illustrator, export both as SVG, run each through SVGLite to strip unnecessary markup, and paste the cleaned paths inside.
Step 2 — Add Light and Dark Styles
Add a <style> block inside the SVG. Define your light-mode appearance as the default, then override it inside
a @media (prefers-color-scheme: dark) block. There are two common approaches.
Color-based approach — same shape, different colors
The simplest case: the icon shape stays the same but its fill and stroke colors change with the theme.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
.bg { fill: #ffffff; }
.fg { fill: #0f172a; }
@media (prefers-color-scheme: dark) {
.bg { fill: #0f172a; }
.fg { fill: #ffffff; }
}
</style>
<rect class="bg" width="32" height="32" rx="4"/>
<text class="fg" x="16" y="22" font-size="18"
text-anchor="middle" font-family="sans-serif">A</text>
</svg>Visibility-based approach — two separate icons
When your light and dark icons are structurally different, wrap each in its own <g> group and toggle visibility with CSS. This is the approach Get Adaptive Favicon uses when you upload separate light and dark images — it wraps them in a single SVG and wires
up the CSS automatically.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
#light { display: block; }
#dark { display: none; }
@media (prefers-color-scheme: dark) {
#light { display: none; }
#dark { display: block; }
}
</style>
<g id="light">
<rect width="32" height="32" rx="4" fill="#f8fafc"/>
<circle cx="16" cy="16" r="8" fill="#334155"/>
</g>
<g id="dark">
<rect width="32" height="32" rx="4" fill="#1e293b"/>
<circle cx="16" cy="16" r="8" fill="#94a3b8"/>
</g>
</svg>Step 3 — Link Favicons From HTML
Inside the <head> of your HTML, reference the legacy .ico fallback,
the adaptive SVG, and PNG variants:
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="32x32"
media="(prefers-color-scheme: dark)" href="/favicon-dark-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="16x16"
media="(prefers-color-scheme: dark)" href="/favicon-dark-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest"> Order matters
Browsers evaluate <link rel="icon"> tags and pick the most capable format they support. By placing favicon.svg after favicon.ico, capable browsers (Firefox, Chromium) prefer the SVG while older browsers silently fall
back to .ico.
The media="(prefers-color-scheme: dark)" attributes on the PNG links add another compatibility layer for browsers that support PNG favicons
with media queries but not SVG favicons.
Verify it in DevTools
After adding the links, open the Network panel in DevTools, filter by Images, and reload the page. You should see favicon.svg being requested instead of favicon.ico.
To test the dark variant without changing your OS theme: DevTools → Rendering tab → Emulate CSS media feature prefers-color-scheme → select dark. The tab icon updates immediately.
Step 4 — Add a Web App Manifest
A site.webmanifest file tells browsers how to treat your site when installed as a Progressive Web App (PWA), including
which icons to use for the home screen launcher on Android and desktop.
{
"name": "Your App Name",
"short_name": "App",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
} Place this file at the root of your server and reference it with <link rel="manifest" href="/site.webmanifest">. Customize name, short_name, theme_color, and background_color to match your brand.
Progressive Enhancement
The beauty of this approach is that it degrades gracefully at every level. No user sees a broken or missing icon — each browser takes the best format it can display.
| Browser / Context | What it gets |
|---|---|
| Modern Chromium / Firefox | favicon.svg with embedded media query — fully adaptive |
| Browsers with PNG + media support | favicon-32x32.png / favicon-dark-32x32.png via <link media="…"> |
| Legacy browsers | favicon.ico with 16x16 and 32x32 embedded |
| iOS Safari home screen | apple-touch-icon.png (180x180) |
| Android / PWA installer | android-chrome-192x192.png and android-chrome-512x512.png via manifest |
Complete File List
A fully-equipped favicon setup includes these files:
| File | Purpose |
|---|---|
favicon.ico | Legacy multi-resolution icon (16x16, 32x32) |
favicon.svg | Adaptive SVG with prefers-color-scheme |
favicon-16x16.png | Small PNG for light mode |
favicon-dark-16x16.png | Small PNG for dark mode |
favicon-32x32.png | Standard PNG for light mode |
favicon-dark-32x32.png | Standard PNG for dark mode |
apple-touch-icon.png | iOS home screen icon (180x180) |
android-chrome-192x192.png | Android PWA icon |
android-chrome-512x512.png | Android PWA icon (high-res) |
site.webmanifest | PWA manifest |
Generate One Automatically
Building all of these files by hand — especially the binary .ico format, correctly-sized PNGs at every resolution, and a properly wired SVG wrapper — is tedious. Get Adaptive Favicon automates the entire pipeline in the browser:
- From an image — Upload your own SVG, PNG, or JPG for both light and dark modes. The tool generates all sizes, writes the adaptive SVG wrapper, and packages everything into a ZIP.
- From text — Choose any of 1,800+ Google Fonts, pick your colors and background shape (square, rounded, or circle), and get a fully rendered adaptive favicon without touching a design tool.
- From an emoji — Pick any emoji in one of six artwork styles (Twemoji, OpenMoji, Blobmoji, Noto, Fluent, Fluent Flat) and turn it into a polished favicon set instantly.
Every generator outputs the same complete ZIP bundle: favicon.ico, favicon.svg, all PNG sizes, apple-touch-icon.png, PWA icons, site.webmanifest, and a ready-to-paste HTML snippet.
Conclusion
An adaptive favicon is a small but meaningful detail that makes your site feel crafted:
- Create
favicon.svgwith an embedded<style>block and a@media (prefers-color-scheme: dark)override. - Keep
favicon.icoas a fallback for legacy browsers. - Add PNG variants with
mediaattributes for additional compatibility. - Include
apple-touch-icon.pngand asite.webmanifestfor mobile and PWA support. - Place all link tags in the correct order in your HTML head.
The whole setup requires zero JavaScript or build steps. If you want to skip the manual work, Get Adaptive Favicon generates the complete bundle for you in seconds.
Further Reading
- Building an adaptive favicon — web.dev
- prefers-color-scheme in SVG favicons — Thomas Steiner (the original discovery)
- SVG favicon browser support — caniuse
- SVGLite — SVG optimizer
- Check your own site's favicon setup