Reusing SVG Icons for Faster Pages

Reusing SVG Icons for Faster Pages

Publish Date: Jun 30
5 0

SVG is an XML-based vector graphics format that supports interactivity and animation. The SVG 1.1 specification spans more than 800 pages, so there's a lot hidden inside.

A little-known SVG feature can make your site faster when you repeat the same SVG images, such as icons.

The Problem: Too Many SVG Images

We recently redesigned the Symfony Packages page that lists every Symfony package (currently 266 and counting) and lets you filter them with a bit of JavaScript.

The page itself is simple, but there is a problem: it is massive. With so many packages, even small elements add up quickly. For example, each package shows five SVG icons. That makes a total of 266 × 5 = 1,330 SVG images, just for icons.

This is not a problem for the total page size. Thanks to HTTP compression, this 1.2MB HTML page only transfers 84KB to clients. However, adding thousands of images slows down parsing and rendering, and it also hurts the JavaScript-based sorting performance.

We could render the SVG icons with <img> elements or as CSS backgrounds, but that approach doesn't let us change the icon's color, a must-have feature in our case. We could also load the list dynamically with JavaScript, but that only defers the rendering performance issue without actually solving it.

Fortunately, SVG defines a feature that allows you to reuse an image as many times as you want within the same HTML page. Our solution displays 1,330 SVG icons while only including 5 SVG definitions in the HTML source.

The Solution: SVG <use> Element

Take this GitHub SVG icon from the Simple Icons collection:

<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
    <path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path>
</svg>
Enter fullscreen mode Exit fullscreen mode

First, add an id attribute to the <path> element inside the <svg>. Let's call it icon-github:

<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
    <path id="icon-github" ...></path>
</svg>
Enter fullscreen mode Exit fullscreen mode

Tip
For complex SVG images with multiple elements, wrap everything in a <g> element and add the id attribute to that grouping element.

Now you can reuse this image unlimited times as follows:

<svg viewBox="0 0 24 24" height="16" width="16" aria-hidden="true">
    <use href="#icon-github"></use>
</svg>
Enter fullscreen mode Exit fullscreen mode

The <use> SVG element takes nodes from within an SVG document and duplicates them elsewhere. It clones the referenced nodes and places them where the <use> element appears, without creating a shadow DOM.

That's it. You can reuse the same SVG image unlimited times on the same page without including its contents repeatedly.

Legacy SVG Attributes

If the href attribute in <use> doesn't work in older browsers, fall back to the legacy xlink:href attribute with the same value. To support both modern and legacy browsers, include both attributes (browsers prioritize href):

<svg viewBox="0 0 24 24" height="16" width="16" aria-labelledby="github-icon-title">
    <title id="github-icon-title">Source code</title>
    <use href="#icon-github" xlink:href="#icon-github"></use>
</svg>
Enter fullscreen mode Exit fullscreen mode

Styling of Reused SVG Images

The <use> element clones the referenced content. CSS properties like fill and stroke can cascade from the <svg> instance, but only if they are not already defined on the source graphics.

If the cloned contents are <path d="..." fill="currentColor">, you can override the color with <svg fill="red">. However, if the cloned contents are <path d="..." fill="black">, you can't override the color because the fill="black" on the source path takes precedence.

That's why it's recommended to remove the fill and stroke attributes from the source SVG paths or set them to currentColor so the cloned icons can be styled later.

Creating SVG Sprites

In our web page, we did the following: render icon definitions once in a hidden container, then reference them throughout the page:

<!-- the browser won't display these icons -->
<div style="display: none">
    <svg><path id="icon-github" ...></path></svg>
    <svg><path id="icon-download" ...></path></svg>
    <svg><path id="icon-calendar" ...></path></svg>
    <!-- ... -->
</div>
Enter fullscreen mode Exit fullscreen mode
{% for package in packages %}
    {# ... #}

    <a class="..." href="...">
        <svg viewBox="0 0 24 24" height="16" width="16" aria-hidden="true" class="...">
            <use href="#icon-github"></use>
        </svg>
        Code
    </a>

        {# ... #}
{% endfor %}
Enter fullscreen mode Exit fullscreen mode

Accessibility tip
If an icon appears next to descriptive text (like "Code" in the example above), mark it as decorative with aria-hidden="true" so screen readers can ignore it. If the icon stands alone (e.g., a button without text), make it accessible with a <title> element and aria-labelledby.

While this approach works well, SVG provides a better way of doing it thanks to the <defs> and <symbol> elements. The <defs> element is a container for graphical objects that you want to define once and use later.

The <symbol> element is not rendered directly; it defines graphical template objects that are rendered only when referenced by a <use> element. It also supports its own viewBox and preserveAspectRatio attributes, making it a self-contained, reusable component.

This collection of definitions inside a single SVG is commonly called an SVG sprite: a single file that includes multiple reusable graphics.

You can replace the previous <div>-based container with an inline sprite like this:

<svg style="display: none;">
    <defs>
        <symbol id="icon-github" viewBox="0 0 24 24">
            <path d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303..."></path>
        </symbol>

        <symbol id="icon-download" viewBox="0 0 24 24">
            <path d="..."></path>
        </symbol>

        <symbol id="icon-calendar" viewBox="0 0 24 24">
            <path d="..."></path>
        </symbol>

        <!-- ... -->
    </defs>
</svg>
Enter fullscreen mode Exit fullscreen mode

It's recommended to add the viewBox attribute to each <symbol> so you don't have to repeat it later. Instead, the icon instances only need height and width:

<svg height="16" width="16" aria-hidden="true" class="...">
    <use href="#icon-github"></use>
</svg>
Enter fullscreen mode Exit fullscreen mode

The SVG sprites can also be defined as external files and reference them on any page via its public URL and id value:

<svg width="16" height="16">
    <use href="/path/to/svg-icons-sprite.svg#icon-github"></use>
</svg>
Enter fullscreen mode Exit fullscreen mode

If you reuse the same icons across many pages, put the SVG sprite in its own file so browsers can cache it and boost performance even further.

Key Takeaways

  • Define each icon once and reuse it with <use> so your DOM stays light and renders faster.
  • Bundle multiple icons into a <symbol>-based sprite, which can be inlined or loaded from an external, cacheable file.

✨ If you enjoyed this or any of my other articles and want to support my work, consider sponsoring me on GitHub 🙌


Comments 0 total

    Add comment