Skip to content

Instantly share code, notes, and snippets.

@cvan
Created April 19, 2023 02:36
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cvan/934829892064eb51498ce5cc95a4a1ad to your computer and use it in GitHub Desktop.
Save cvan/934829892064eb51498ce5cc95a4a1ad to your computer and use it in GitHub Desktop.
reuse SVG multiple times on same web page

View DEMO

demo of rendering templated SVGs by defining the shape once

Creating SVG templated elements

Why? Less code is sent down the wire to the user ("the client," the browser). This means faster page load times since it's less code. As a principle, a smaller and flatter DOM structure is going to perform better in the browser. In general, the larger the DOM node tree, the slower, manipulations to the structure will be more expensive by the browser's layout system.

Define a template <svg> with a direct child of <symbol id="[ID]">. Example:

<svg style="display: none" viewBox="0 0 16 16">
  <symbol id="my-template-lock-icon">
    <path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z" />
  </symbol>
</svg>

Hiding template SVG elements

The SVGs that are defined should be hidden, since we're not rendering them. We're rendering instances of them, elsewhere in the web-page document.

Depending on your browser, the SVG will be visually hidden:

<svg hidden>
  <symbol >
    <!-- shapes goes here. path(s), circle(s), rect(angle)s, (g)roups, etc. -->
  </symbol>
</svg>

To set an accessibile baseline for screen readers and accessibility suppport devices, include both aria-hidden="true" and `focusable="false" attributes.

<svg hidden aria-hidden="true" focusable="false">

Though there is good browser support for the hidden attribute, depending on your web application, your template SVGs may require a inline style attribute to forcibly hide the <svg>.

Since we will render the icon only by referencing the template SVG, we want to hide it. To ensure 100% browser compatibility, we will want to use CSS to make sure it's hidden. Even with a style declared in an external or inline CSS stylesheet, if the template <svg> is rendered before the CSS is downloaded, parsed, applied, and then rendered, you'll likely see the icon appear against a white background — known as Flash of Unstyled Content [FOUC].

To ensure that there's no way it could ever be rendered, you can set the CSS in the style attribute.

Going one step further, if the page has other CSS (e.g., a browser style reset [e.g., normalize.css], TailwindCSS, or CSS styles are defined in a React component [i.e., CSS-in-JS, CSS Modules, etc.]) that set svg { display: {inline-block,block,grid,inline-grid,flex,inline-flex,…} !important; }, then the specificity of that rule will need to be unset with !important.

<svg  style="display: none !important"><symbol id=""></svg>

Now anywhere else in the document, you may re-use the vector graphic by using the <use> tag with the xlink:href attribute.

Since vector graphics inherently do not degrade in quality when resizing, you could increase the dimensions by defining width and height attributes on the <svg>.

You can define the sizes or other styles using CSS, which can override the intrinsic dimensions set by width and height. Defining width and height are recommended:

  • inline: <svg style="width: 16px; height: 16px;" …><use … /></svg>
  • intrinsic: <svg width="16" height="16" …><use … /></svg> - integer values are assumed to be in px; % percentages are allowed, as well as "auto"

If the sizes are not set, the browser will still render the SVG, but a Content Layout Shift (CLS) will likely occur. CLS is the term for when elements shift around as the browser loads and renders a web page. Tools and services like Calibre Analytics, Vercel Analytics, Netlify Analytics, WebPageTest, SiteSpeed.io, Google PageSpeed, or Google Lighthouse can measure this for you. A high CLS score number can be a jarring experience especially for users accessing your site from a memory-constraine device or experience a spotty network connection.

Remember: only the template <svg> need the inline style to hide them from the page.

Per MDN documentation on width attribute on <svg> tags:

Note: width has no effect on use elements, unless the element referenced has a viewBox - i.e. they only have an effect when use refers to a svg or symbol element.

on the tag or elsewhere defined in a <style> tag or an externally linked stylesheet]).

<svg width="24" height="24" viewBox="0 0 16 16">
  <use xlink:href id="my-template-lock-icon" />
</svg>

The usage of <use xlink:href="ID"> will render in that place the value of the original <svg id="ID">. it instead of <svg …><use xlink:href="EXAMPLE_ID" /></svg>.

the <svg id="EXAMPLE_ID"> will be auto-inserted when referenced by the <svg> can be reused elsewhere in the document

… gets turned into this in the document when:

<svg>
  <use xlink:href="my-icon">
    <#shadow-root>
      <svg id="my-icon" viewBox="0 0 16 16" viewBox="">
        <path >
      </svg>
    </#shadow-root>
  </use>
<svg>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-bell">
  <path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"></path>
</svg>

… gets turned into this in the document:

<svg>
  <use xlink:href="sprite.svg#my-icon">
    <#shadow-root>
      <svg id="my-icon" viewBox="">
        <path >
      </svg>
    </#shadow-root>
  </use>
<svg>

![[Pasted image 20230418183509.png]]

demo: https://play.tailwindcss.com/qzBDUYmrbX


<!--
Place these reusable SVG icons at the bottom near </body>.
Or in your React App or Next.js' layout component:
at the page-level or app-level, whichever makees more sense, which will depend on where the icon components are often repeated in the same view.
-->
<svg hidden style="display: none" class="my-icon my-icon-lock" aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16">
  <symbol id="my-icon-lock">
    <path fill="currentcolor" d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z" />
  </symbol>
</svg>
<svg hidden style="display: none" class="my-icon my-icon-star" aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16">
  <symbol id="my-icon-star">
    <path fill="currentcolor" d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z" />
  </symbol>
</svg>
<svg hidden style="display: none" class="my-icon my-icon-chat" aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16">
  <symbol id="my-icon-chat">
    <path fill="currentcolor" d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.458 1.458 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h4.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" />
  </symbol>
</svg>

<main class="space space-y-8 bg-slate-200 px-4 pb-8 pt-8">
  <h1 class="w-[fit-content] rounded-lg bg-green-300/20 p-3 px-6 text-center text-lg font-light uppercase leading-snug tracking-widest text-slate-800/75 shadow-lg">Seavan&rsquo;s Tasty Vegan Blog</h1>

  <article class="space space-y-4 rounded-lg bg-white p-6 shadow-xl shadow-black/5 ring-1 ring-slate-700/10">
    <h2 class="line-clamp-1 text-3xl/10">Mango Strawberry Avocado Coriander Lime Tortilla Chip Dip</h2>
    <p class="line-clamp-4">Cozy butternut one bowl sesame soba noodles crunchy seaweed dark and stormy black beans vine tomatoes crispy iceberg lettuce green tea with soy milk lime mango crisp avocado basil pesto sweet potato black bean burrito golden cayenne pepper cozy cinnamon oatmeal mocha chocolate coconut sugar seasonal Malaysian maple orange tempeh tahini drizzle crispy scotch bonnet pepper black bean chili dip naga viper salad soup. Edamame lemon tahini dressing cranberry spritzer bruschetta cinnamon mangos parsley lemon lime minty Italian pepperoncini second course Bulgarian carrot pine nuts cool off walnut pesto tart paprika. Ultimate cilantro lime vinaigrette burritos crumbled lentils leek coconut spicy zesty tofu pad thai red amazon pepper salted ginger lemongrass agave green tea dark chocolate Caribbean red habanero Thai sun pepper.</p>
    <footer class="space space-y-4">
      <ul class="space space-y-2">
        <li>
          <strong class="inline-grid grid-flow-col place-content-center items-center gap-1.5 bg-red-100 p-2 text-red-600">
            Premium Article
            <svg class="order-first" focusable="false" width="16" height="16" viewBox="0 0 16 16">
              <use xlink:href="#my-icon-lock" />
            </svg>
          </strong>
        </li>
        <li>
          <strong class="inline-grid grid-flow-col place-content-center items-center gap-1.5 bg-yellow-100 p-2 text-yellow-500">
            Save to Favorites
            <svg class="order-first" focusable="false" width="16" height="16" viewBox="0 0 16 16">
              <use xlink:href="#my-icon-star" />
            </svg>
          </strong>
        </li>
      </ul>

      <h3 class="grid grid-flow-col place-items-center justify-center gap-3 bg-blue-100 p-2 text-2xl font-bold text-blue-500">
        Discussion
        <svg class="order-first" focusable="false" width="24" height="24" viewBox="0 0 16 16">
          <use xlink:href="#my-icon-chat" />
        </svg>
      </h3>
    </footer>
  </article>

  <article class="space space-y-4 rounded-lg bg-white p-6 shadow-xl shadow-black/5 ring-1 ring-slate-700/10">
    <h2 class="line-clamp-1 text-3xl/10">Delightful Blueberry Scones Candy Cane Winter</h2>
    <p class="line-clamp-4">Blood orange smash elderberry lemonade zest soba noodles street style Thai basil tacos sleepy morning tea mediterranean Chinese five-spice powder orange cauliflower Caribbean red habanero green grapes mangos red grapes sweet potato black bean burrito a delicious meal açai delightful blueberry scones tempeh mediterranean luxury bowl strawberry mango smoothie black bean chili dip potato burritos bananas farro platter. Tasty avocado basil pesto mocha chocolate edamame cherry bomb pepper shiitake mushrooms grains peanut butter matcha Mexican fiesta walnut pesto tart crunchy lemon lime minty muffins cocoa Thai basil curry hot dragon fruit roasted peanuts eating together avocado dressing drizzle tofu salad.</p>
    <footer class="space space-y-4">
      <ul class="space space-y-2">
        <li>
          <strong class="inline-grid grid-flow-col place-content-center items-center gap-1.5 bg-red-100 p-2 text-red-600">
            Premium Article
            <svg class="order-first" focusable="false" width="16" height="16" viewBox="0 0 16 16">
              <use xlink:href="#my-icon-lock" />
            </svg>
          </strong>
        </li>
        <li>
          <strong class="inline-grid grid-flow-col place-content-center items-center gap-1.5 bg-yellow-100 p-2 text-yellow-500">
            Save to Favorites
            <svg class="order-first" focusable="false" width="16" height="16" viewBox="0 0 16 16">
              <use xlink:href="#my-icon-star" />
            </svg>
          </strong>
        </li>
      </ul>

      <h3 class="grid grid-flow-col place-items-center justify-center gap-3 bg-blue-100 p-2 text-2xl font-bold text-blue-500">
        Discussion
        <svg class="order-first" focusable="false" width="24" height="24" viewBox="0 0 16 16">
          <use xlink:href="#my-icon-chat" />
        </svg>
      </h3>
    </footer>
  </article>
</main>
svg:not(:root) {
  overflow: hidden;
}

.icon {
  display: inline-block;
  fill: currentcolor;
  overflow: visible;
  vertical-align: text-bottom;
}

Bonus: Alternative Solution: Link to external .svg files

You can link to a particular symbol defined outside of the HTML document. The external .svg file must contain the correct symbol definitions (<symbol id="…">). Then, as long as the file can load, the vectors can be rendered anywhere in your document.

Due to some default security policies (e.g., Content-Security-Policy [CSP] HTTP response headers), it is generally safer to include the SVG within the HTML document.

See an example below, which is copied from a reference below.

<svg>
  <use xlink:href="sprite.svg#my-icon" />
</svg>

… gets turned into this in the document:

<svg>
  <use xlink:href="sprite.svg#my-icon">
    <#shadow-root>
      <svg id="my-icon" viewBox="">
        <path >
      </svg>
    </#shadow-root>
  </use>
<svg>

related references

articles

examples

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment