Published on 29 Sep 2025
Safari fallback for SVG + CSS
I think SVG+CSS is by far the best solution for rendering icons in HTML:
- It reduces DOM size.
- It caches icons in CSS.
- You can use full power of CSS to change shapes: CSS variables, animations, transitions.
See SVG+CSS intro.
The biggest problem is the Safari browser.
What is the problem
The problem is, Safari browser does not support path() function for d attribute in CSS.
Path of shapes is the biggest part of most icons. If it cannot be moved to CSS, the whole idea of SVG+CSS becomes pointless.
But there is hope.
Safari Technology Preview does support it and did support it since 2024. Feature just didn’t make it into stable release yet.
So there is hope support for path() function in CSS will be available in a future Safari update.
Solution
The solution I came up with is to use Vue, Svelte, React and SolidJS components that:
- Detect an outdated browser
- Load fallback SVG asynchronously from an external source
- Render fallback SVG once icon is loaded
This is a perfect use case for Iconify API.
Detecting browser
Detecting browser is simple: check of CSS supports d: path("M0 0"):
function supportsCSS() { try { return window.CSS.supports('d: path("M0 0")'); } catch (error) { return true; }}Loading fallback SVG
It does not make sense to include two version of the same icon in website scripts:
- SVG+CSS version for modern browsers
- SVG only version for Safari
Serving icons is very much platform dependent, works differently in different frameworks and different websites. It is very complex to implement for custom icons.
However, for icons available in Iconify icon sets, there is already a solution: Iconify API.
How does it work?
Icon components have 3 mandatory parameters:
- Content of icon to render for modern browsers
- viewBox size
- Fallback icon name for Safari browser
First two parameters are used to generate SVG for modern browsers.
Last parameter is name of icon to load and render from Iconify API for Safari.
Process
Component checks if browser supports path() function for d attribute in CSS.
If it does, combines viewBox and content into a simple SVG, renders it immediately.
If it does not:
- Triggers asynchronous loading of fallback icon from Iconify API
- Renders SVG+CSS
When data from API is returned, it replaces icon content with content from icon loaded from API.
Vue example
Usage example, including all CSS:
<script setup lang="ts">import { type CSSIconComponentViewbox, Icon } from '@iconify/css-vue';
const grid24: CSSIconComponentViewbox = { width: 24, height: 24,};
const tablerHomeIcon = `<g class="tabler-group"> <path class="tabler-home-path1" /> <path class="tabler-home-path2" /> </g>`;
const msDrafts = `<path class="ms-drafts" />`;</script><template> <div> <p>Test icons:</p> <Icon :content="tablerHomeIcon" :viewBox="grid24" fallback="tabler:home" /> <Icon :content="msDrafts" :viewBox="grid24" fallback="material-symbols:drafts-rounded" /> </div></template>
<style>svg { width: 24px; height: 24px;}
.tabler-group { fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;}.tabler-home-path1 { d: path('M5 12H3l9-9l9 9h-2M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7');}.tabler-home-path2 { d: path('M9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6');}
.ms-drafts { fill: currentColor; d: path( 'm13.025 1.6l8.025 4.8q.45.275.7.75t.25 1V19q0 .825-.587 1.413T20 21H4q-.825 0-1.412-.587T2 19V8.15q0-.525.25-1t.7-.75l8.025-4.8q.475-.275 1.025-.275t1.025.275M12 12.65L19.8 8L12 3.35L4.2 8zm-1.025 1.725L4 10.2V19h16v-8.8l-6.975 4.175q-.475.275-1.025.275t-1.025-.275M13.025 19H20H4z' );}</style>Users with modern browsers see this:
<div> <p>Test icons:</p> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <g class="tabler-group"> <path class="tabler-home-path1"></path> <path class="tabler-home-path2"></path> </g> </svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path class="ms-drafts"></path> </svg></div>Safari users see this:
<div> <p>Test icons:</p> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" > <path d="M5 12H3l9-9l9 9h-2M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7" ></path> <path d="M9 21v-6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6"></path> </g> </svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path fill="currentColor" d="m13.025 1.6l8.025 4.8q.45.275.7.75t.25 1V19q0 .825-.587 1.413T20 21H4q-.825 0-1.412-.587T2 19V8.15q0-.525.25-1t.7-.75l8.025-4.8q.475-.275 1.025-.275t1.025.275M12 12.65L19.8 8L12 3.35L4.2 8z" ></path> </svg></div>Implementation
Now that fallback is figured out, the remaining issue is implementing icons:
- Converting SVG to SVG+CSS
- Generating components
But that’s for a different article.