Skip to content
Back to Journal
Fundamentals

Dark Mode Favicon: One SVG That Adapts (and Where It Can't)

SM
SVG Maker AI
13 min read

A dark mode favicon is one SVG file carrying a few lines of CSS: a @media (prefers-color-scheme: dark) rule inside the icon swaps its colours when the browser is dark. That part works in Chrome, Edge and Firefox. What most tutorials leave out is everything around it — Safari 26 now shows SVG favicons but, on macOS at least, ignores the dark rule; Google Search does not list SVG among its favicon formats; the iOS home-screen icon cannot adapt; and browsers hold on to old favicons so firmly that a hard refresh often changes nothing. This is the whole setup, file by file.

Why a favicon is not visible in dark mode

Most logos are drawn dark on a transparent background. A favicon has no background of its own: it sits on whatever the browser paints behind it — the tab strip, the bookmarks bar, the history list, a new-tab tile. On light browser chrome that is fine. On a dark tab strip a near-black glyph lands on charcoal and all but disappears.

There are two fixes. Give the icon its own background — a tile — so it carries its contrast with it, or make the glyph change colour with the theme. A PNG can only do the first. An SVG can do both, because an SVG can contain CSS, and CSS can contain a media query.

The dark mode favicon: one SVG with a media query

Here is a complete favicon.svg. The colour is set once on the root element, every shape paints with currentColor, and the dark rule changes that single value.

favicon.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <style>
    svg { color: #18181b; }
    @media (prefers-color-scheme: dark) {
      svg { color: #fafafa; }
    }
  </style>
  <path d="M8 24C8 14 14 8 24 8" fill="none" stroke="currentColor"
        stroke-width="4" stroke-linecap="round"/>
  <rect x="2" y="20" width="10" height="10" rx="2" fill="currentColor"/>
  <rect x="20" y="2" width="10" height="10" rx="2" fill="currentColor"/>
</svg>

The browser evaluates that media query when it renders the icon. Your page’s stylesheet is not involved, and nothing in your JavaScript can reach inside the file once it is loaded as an image. A few rules keep it working:

  • viewBox, on a small grid. The viewBox is what lets one file scale to every size. A 32-unit grid maps exactly onto 32 px and halves cleanly to 16, so even-numbered coordinates land on whole pixels at both sizes; the reasoning is in designing responsive icons.
  • Everything inline. According to caniuse, Chromium renders SVG favicons in secure static mode: no scripts, no external files, and SMIL animation evaluated at time zero. A linked font or image will not load, so convert any lettering to paths.
  • The right MIME type. caniuse also notes that Chromium and Firefox require the file to be served as image/svg+xml. If your server sends anything else, those browsers will not use it — the same class of failure covered in why an SVG is not showing.
  • Small. The file above is under 500 bytes.

If you want to adjust the glyph visually, build it from basic shapes or paste existing markup into the code panel of the SVG editor, tidy it there, export the .svg, and copy its shapes into this template. The <style> block and the currentColor fills are worth writing by hand — it is ten lines.

Which theme the icon actually follows

This is where most write-ups are vague, and it matters, because the three engines do not agree.

  • Firefox follows its own browser theme — the colour of the tab strip — not the page’s Website appearance setting. Mozilla made favicon media queries match the browser theme in Firefox 101 (bug 1764354). With the default theme that tracks the operating system; with a dark Firefox theme on a light OS, the icon gets the dark rule, which is correct, because it is sitting on a dark tab strip.
  • Chrome and Edge apply the dark rule. In our own test, an SVG image in headless Chrome 156 switched with the preference both ways. For the favicon itself, Paweł Grzybek’s tests found Chrome only picks up a theme change after a reload. Whether Chrome’s own Light/Dark appearance setting, as distinct from the OS setting, drives it is something we could not confirm.
  • Safari 26 shows the SVG but ignores the media query, so it always renders the default colours. WebKit bug 309949 reproduces it on Safari 26.3, was confirmed by WebKit engineers in March 2026, and is still open; the thread notes that WebKitGTK browsers such as Epiphany behave the same way.

Do not expect your site’s own theme toggle to reach the favicon. The icon is not part of your page’s DOM: Firefox renders it against the browser theme, Safari ignores the query entirely, and we found nothing showing Chromium taking it from the page. That is the right behaviour anyway — when your site switches to dark, the tab strip does not, and a favicon that followed your toggle would put a light glyph on a light tab.

SVG favicon browser support in 2026

The search results are stale here: many ranking guides still say Safari does not support SVG favicons. The current caniuse table, read on 30 October 2026, lists Safari 26.0 and later as supported on both macOS and iOS, and every version up to 18.7 as unsupported. WebKit announced the change in its Safari 26 beta notes.

SVG favicon support from caniuse, read 30 October 2026, and whether the prefers-color-scheme rule inside the icon is applied.
BrowserShows an SVG faviconApplies the dark rule
Chrome, Edge (desktop)80 and laterYes; reported to switch only after a reload
Firefox (desktop)41 and later (partial before)Yes; follows the Firefox theme
Safari (macOS)26.0 and laterNo; always the default colours (WebKit bug 309949)
Safari (iOS)26.0 and laterNot verified; the WebKit bug was reported on macOS
Samsung Internet13.0 and laterNot verified

Showing the SVG and switching it are separate questions. Safari users in dark mode will see your default colours, so the default has to survive a dark tab strip too. The robust answer is to put the glyph on its own tile and let the dark rule merely flip the tile:

favicon.svg (tiled)
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <style>
    .tile { fill: #18181b; }
    .mark { color: #fafafa; }
    @media (prefers-color-scheme: dark) {
      .tile { fill: #fafafa; }
      .mark { color: #18181b; }
    }
  </style>
  <rect class="tile" width="32" height="32" rx="7"/>
  <g class="mark" transform="translate(6 6) scale(0.625)">
    <path d="M8 24C8 14 14 8 24 8" fill="none" stroke="currentColor"
          stroke-width="4" stroke-linecap="round"/>
    <rect x="2" y="20" width="10" height="10" rx="2" fill="currentColor"/>
    <rect x="20" y="2" width="10" height="10" rx="2" fill="currentColor"/>
  </g>
</svg>
index.html — inside head
<!-- 1. Raster fallback: older browsers and Google Search -->
<link rel="icon" href="/favicon-32x32.png" type="image/png" sizes="32x32">

<!-- 2. The adaptive icon. Declared last, so browsers that
        understand SVG prefer it over the PNG -->
<link rel="icon" href="/favicon.svg" type="image/svg+xml" sizes="any">

<!-- 3. iOS home screen: a 180 x 180 PNG with a solid background -->
<link rel="apple-touch-icon" href="/apple-touch-icon.png">

<!-- 4. Install icons for Android and desktop PWAs -->
<link rel="manifest" href="/site.webmanifest">

Order and attributes both do work here. MDN describes the selection: the browser weighs media, type and sizes, uses the last of several equally suitable icons, and moves to the next candidate if the chosen one turns out to be unusable. So the PNG goes first and the SVG last, and the type attribute lets a browser without SVG support skip the file without downloading it.

The explicit sizes="32x32" on the raster link is deliberate. Evil Martians’ favicon guide, which puts an ICO in the same slot, records the history in its changelog: sizes="any" on the fallback was added in 2021 because Chrome was picking the ICO over the SVG, then replaced with 32x32 in 2023 because it made Chrome download both files. Next.js emits the same PNG-then-SVG pattern, as the Next.js section below shows.

By convention, browsers also request /favicon.ico from the site root. A file there costs nothing and covers any page that forgets the tags. If you have PNGs but no ICO tool, Pillow will pack one:

terminal
# Optional: pack the 48 px PNG into a 16/32/48 favicon.ico
# (needs Python with Pillow: pip install pillow)
python3 -c "from PIL import Image; Image.open('favicon-48x48.png').save('favicon.ico', sizes=[(16, 16), (32, 32), (48, 48)])"

PNG fallbacks, Google Search and Icon Studio

The raster files are not legacy baggage. Google’s favicon guidelines (last updated 27 October 2026) list BMP, GIF, ICO, PNG, JPEG, PPM and TIFF as supported formats; SVG is not among them. Google reads rel="icon" and apple-touch-icon links, wants a square at least 8 px across, recommends larger than 48 px, shows one favicon per hostname and says recrawling can take days to weeks. Which of your links it picks is not documented, so make every raster file a good one.

Icon Studio on the favicon generator page makes that ladder. To be exact about what it does: it builds a PNG-only ZIP in your browser with 16, 32 and 48 px favicons, a 180 px apple-touch-icon, and 192 and 512 px android-chrome icons. It does not produce an SVG favicon, a dark-mode icon, an .ico, a manifest or an HTML snippet. Use it for the fallbacks and write favicon.svg by hand as above. It starts from a library icon or an uploaded .svg, not a PNG.

One practical step: upload a copy of your icon with the <style> block deleted and a literal fill on each shape. The PNGs then come out the same whatever theme the machine doing the export is in. Its solid background and rounded mask controls give you the tile. Then open the exported PNGs themselves at 100%, not just the on-screen preview, and if the mark comes out cropped or too small, adjust the scale and padding controls and export again.

apple-touch-icon and manifest icons cannot adapt

The apple-touch-icon is a 180 × 180 PNG. A bitmap has no stylesheet, so there is nothing to switch, and we found no documented way to give a home-screen icon a dark variant. Design it once with a solid background so it reads on any wallpaper; Evil Martians suggest roughly 20 px of padding around the mark. WebKit’s Safari 26 notes say SVG icons can now represent a web app on the Home Screen and in the Dock as well, but keep the PNG for older iOS versions, and given bug 309949, do not expect the dark rule to apply there.

site.webmanifest
{
  "name": "Example Studio",
  "short_name": "Example",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#18181b",
  "icons": [
    { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
  ]
}

The manifest is the same story. MDN’s icons reference describes src, sizes, type and a purpose of any, maskable or monochrome — there is no colour-scheme variant. We would keep the media-query SVG out of the manifest: nothing we found documents an installed icon re-rendering when the theme changes.

The two-file alternative: media on the link

If the mark cannot sensibly be an SVG, you can ship two rasters and choose between them in HTML:

index.html — two rasters
<!-- Chromium honours media on icon links and swaps live.
     Firefox ignores media here, so the light icon goes last:
     it is the one a browser falls back to. -->
<link rel="icon" href="/favicon-dark-32x32.png" type="image/png" sizes="32x32"
      media="(prefers-color-scheme: dark)">
<link rel="icon" href="/favicon-light-32x32.png" type="image/png" sizes="32x32"
      media="(prefers-color-scheme: light)">

Chromium has honoured media on icon links since Chrome 91 (Chrome Platform Status), and its launch announcement says the icon updates when the preference changes. Firefox ignores the attribute on icon links — bug 1603885 is still open — which is why the light file goes last: it is the one the last-wins tie-break picks when a browser disregards the query. Some guides list Firefox as fully supporting this; it does not. We could not confirm Safari’s behaviour either way. For most marks the single SVG is fewer files and works in Firefox, so treat this as the fallback plan.

Why the old favicon sticks

You ship the new icon, reload, and the old one is still there. Browsers keep favicons in their own store, separate from the page cache — Chrome in a Favicons database in the profile, Firefox in favicons.sqlite — so a hard refresh reloads the page without necessarily replacing the icon. The dependable fix is a new URL:

index.html — versioned
<!-- Same file, new URL: every browser treats this as a new icon.
     Bump the number when the artwork changes, not on every deploy. -->
<link rel="icon" href="/favicon.svg?v=2" type="image/svg+xml" sizes="any">
  • Long cache lifetimes need versioned URLs. Some guides recommend a year-long immutable cache header on favicons. On an unversioned /favicon.svg that guarantees the stale-icon problem.
  • Do not bump it on every deploy. Google asks for a stable favicon URL. Change it when the artwork changes.
  • Test the file, then the tab. Open /favicon.svg directly and switch the preference in DevTools’ Rendering panel to check both colourways. Then change the OS or browser theme, reload, and check the real tab in a fresh private window.

Dark mode favicon in Next.js App Router

The App Router’s icon file conventions produce the right markup if you name the files carefully. Numbered icons sort lexically, so icon1.png is emitted before icon2.svg. We built this layout with Next.js 16.3:

file layout and generated head
app/
  icon1.png       32 x 32 PNG fallback
  icon2.svg       the adaptive favicon.svg from above
  apple-icon.png  180 x 180, solid background
  manifest.js     served as /manifest.webmanifest
public/
  favicon.ico     optional; served at /favicon.ico with no link tag

<!-- What a Next.js 16.3 build put in <head>.
     The query strings are content hashes. -->
<link rel="icon" href="/icon1.png?ba45575e40a3b61e" type="image/png" sizes="32x32"/>
<link rel="icon" href="/icon2.svg?7b6012687dcd3b98" type="image/svg+xml" sizes="any"/>
<link rel="apple-touch-icon" href="/apple-icon.png?1efdaad3cb383471" type="image/png" sizes="180x180"/>
<link rel="manifest" href="/manifest.webmanifest"/>

Those query strings are content hashes — editing the SVG and rebuilding changed its hash — so the caching problem above is handled for you, and the SVG was served as image/svg+xml. One detail: an app/favicon.ico is emitted with sizes="any", the pairing Evil Martians found makes Chrome fetch both files, so an ICO is better placed in public/, where it is served from the root without a tag. The manifest can live in app/manifest.js:

app/manifest.js
// app/manifest.js
export default function manifest() {
  return {
    name: 'Example Studio',
    short_name: 'Example',
    start_url: '/',
    display: 'standalone',
    background_color: '#ffffff',
    theme_color: '#18181b',
    icons: [
      { src: '/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' },
      { src: '/android-chrome-512x512.png', sizes: '512x512', type: 'image/png' },
    ],
  }
}

For the rest of the SVG-in-Next.js picture — imports, next/image and inline components — see SVG in Next.js.

Where to go next

Build the PNG ladder with Icon Studio on the favicon generator page, then add the hand-written favicon.svg from this post. If you need the mark itself, the AI icon generator makes a starting SVG from a prompt in the dashboard (sign-in required, one credit per generation), and the SVG editor is the place to tidy it. For installable apps, the app icon generator covers the iOS and Android icon sets.

For the CSS side of theming SVG — currentColor, custom properties and prefers-color-scheme on inline icons — read how to style SVG with CSS. For legibility at 16 px, see designing responsive icon systems.

Frequently asked questions

How do I make a favicon change in dark mode?
Use an SVG favicon with a style block inside it. Set the default colours, add a @media (prefers-color-scheme: dark) rule that changes them, and link the file with rel="icon" and type="image/svg+xml" after a PNG fallback. Chrome, Edge and Firefox apply the dark rule. Safari 26 on macOS shows the SVG but keeps the default colours, so the default must stay legible on a dark tab strip as well.
Why is my favicon not visible in dark mode?
A favicon has no background of its own, so a dark logo on a transparent background sits directly on the browser's tab strip. When the browser theme is dark, the glyph lands on charcoal and disappears. Either give the icon its own solid tile so it carries its contrast everywhere, or use an SVG favicon whose colours switch with a prefers-color-scheme media query.
Does Safari support SVG favicons?
Yes, from Safari 26. caniuse, read on 30 October 2026, lists Safari 26.0 and later on macOS and iOS as supported and every version up to 18.7 as unsupported. Safari 26 does not apply a prefers-color-scheme rule inside the SVG, though: WebKit bug 309949, reproduced on Safari 26.3 for macOS, shows it always rendering the default colours. Keep a PNG fallback for older versions.
Should a favicon be SVG or PNG?
Ship both. The SVG is sharp at every size and is the only favicon format that can switch colours for dark mode. PNG is still needed: Google Search's favicon guidelines list BMP, GIF, ICO, PNG, JPEG, PPM and TIFF but not SVG, older browsers cannot read SVG favicons, and the iOS apple-touch-icon must be a PNG. Link the PNG first and the SVG last.
Does a dark mode favicon follow the OS setting or my site's theme toggle?
Not your site's toggle. Firefox renders the favicon against its own browser theme, which tracks the operating system by default. Chrome and Edge apply the colour-scheme preference, reportedly only after a reload, and Safari 26 ignores the rule altogether. The favicon is drawn by the browser interface rather than your page, and the tab strip does not change colour when your site switches theme.
Why is my old favicon still showing?
Browsers store favicons separately from the page cache, Chrome in a Favicons database and Firefox in favicons.sqlite, so a hard refresh may not replace the icon. Give it a new URL, such as favicon.svg?v=2, when the artwork changes. Avoid year-long immutable cache headers on an unversioned favicon URL, and do not change the URL on every deploy, because Google asks for a stable favicon URL.
How do I add a dark mode favicon in Next.js?
In the App Router, put a 32 by 32 PNG at app/icon1.png and the adaptive SVG at app/icon2.svg. Numbered icons sort lexically, so Next.js emits the PNG link first and the SVG link last, each with a content-hash query string that changes when the file does. Add app/apple-icon.png for iOS, and keep any favicon.ico in public rather than app.
Can the apple-touch-icon change in dark mode?
Not through any mechanism we could find documented. The apple-touch-icon is a 180 by 180 PNG, and a bitmap has no stylesheet or media query to switch. Design it once with a solid background so it reads on any wallpaper. The same goes for web app manifest icons, whose purpose field offers any, maskable and monochrome but no colour-scheme variant.
ShareLinkedInXReddit