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, hover, animations, transitions.

See SVG+CSS intro.

The biggest problem is Safari browser.

What is the problem

The problem is, Safari browser does not support path() function for d attribute in CSS.

At the time of writing this article, the latest version of Safari is 26.0.

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 for at least half a year before Safari 26.0 was released.

Unfortunately feature did not make it in 26.0 release, but there is hope it will be available in future Safari update.

Solution

A solution I came up with is to use Vue/Svelte/React components that:

  • Detect outdated browser
  • Load full SVG asynchronously from an external source
  • Render full SVG for outdated browser 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;
    }
}

Why function returns true on error? Exception will happen if that function is ran on server side. For server side rendering assuming browser supports SVG+CSS.

Loading full 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 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 SVG+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.

Related articles