2

I have a simple external SVG file circles.svg that contains related CSS in <style> tag:

<svg id="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMinYMin meet" viewBox="0 0 500 500">
  <style type="text/css">
    .circle {
      fill: green;
    }

    .use {
      fill: blue;
      scale: 2;
      transform-origin: center;
    }
  </style>
  <circle
    id="circle"
    class="circle"
    cx="250"
    cy="250"
    r="120"
  />
  <use class="use" href="#circle" y="120"/>
</svg>

Then in HTML I reference this SVG file using <svg><use> tags:

<div>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
    <use href="circles.svg#svg"></use>
  </svg>
</div>

Chrome renders it as two green intersecting circles, second twice in size than the other. That's the expected behavior. But Firefox renders two black circles identical in size. It means it totally ignores styles.

enter image description here

  1. Why that happens?
  2. Is there a way to overcome it, keeping styles inside an external SVG file and referencing with <svg><use> tags?

UPDATE

I can't create code smaple right here because it requires to upload somewhere svg file. Image upload here doesn't accept svg's. Loading svg from other service (domain) not permitted. So I've created public sandbox to be able to test and play with.

Sandbox: https://codesandbox.io/p/sandbox/s7szqy

8
  • I've tried your HTML code. It shows nothing, no matter what the browser is. When I load your SVG file alone, it is showing as intended in all browsers. Also, when I simply use <img src="file.svg"/> in HTML, the same SVG is also showing as intended. The problem is your HTML, not SVG. Commented yesterday
  • Please turn your code into a runnable snippet so we can see the problem - this means you will have to store the svg file somewhere where we can use it. Commented yesterday
  • I created a file with the svg as you have given it and run your code. Edge/Chrome and FF show the same - two non-intersecting green circles. And, yes, if the svg is inline then I get two circles intersecting, one twice the size of the other. Commented yesterday
  • @AHaworth I've updated post and added link to sandbox. Strange that you got two non intersecting circles because use element got it's Y axis position not from CSS but from y attribute set to 120. But that's minor thing in this case. Commented yesterday
  • @SergeyAKryukov I've updated the post and added link to sandbox where you can check whether there are some HTML or other problems. Let me know if you find some. I know that loading svg file directly in browser or with img tag as well as with object tag gives expected result. But I am frustrated due to svg-use reference problem. Because as far as I know it is the only way pass CSS variables from page to external SVG. Commented yesterday

2 Answers 2

1

You're facing multiple problems:

  1. external <use> references introduce many cross-browser rendering issues (see appendix) – mainly due to very strict (but not consistent) content policies. To put it differently: Many SVG features just won't work in external SVGs
  2. Support for loading entire SVGs via <use> is relatively new – to this date (2025) you probably encounter unpredictable rendering results

Workarounds 2025

prefer classic symbol/def loading leveraging CSS vars <style>, <clipPath>, <mask> and other tags are often omitted when using external references.

Inline reusable SVG assets

You may prefer inlined symbol/defs references by appending the "spritesheet" markup to your HTML DOM.
Native Web components (dynamically loading your asset SVGs) might also be a convenient alternative.

Refactor your definitions leveraging CSS variables

Instead of loading an entire svg, you may also group your reusable asset responding to CSS variables:

SVG external (circlesV.svg)

<svg  xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">

  <!--basic shape -->
  <defs>
    <circle id="circleEl" cx="250" cy="250" r="120" />
  </defs>

  <!-- symbol: loaded via use -->
  <symbol id="circles">
    <use href="#circleEl" style="fill:var(--fill1)" />
    <use href="#circleEl" y="120" style="fill:var(--fill2); scale:var(--scale); transform-origin:center;" />
  </symbol>

</svg>

HTML usage

  <svg viewBox="0 0 500 500">
    <use href="circlesV.svg#circles" style="--scale:2; --fill1:green; --fill2:blue" />
  </svg>

We can't replicate external SVG use references in SO snippets due to browsers strict content policies – even if we're loading external SVGs from a cdn allowing cross origin access.

Demo – using inlined assets:

<svg viewBox="0 0 500 500">
    <use href="#circles" style="--scale:2; --fill1:green; --fill2:blue" />
  </svg>

  <svg viewBox="0 0 500 500">
    <use href="#circles" style="--scale:0.5; --fill1:purple; --fill2:orange" />
  </svg>
  
  <!-- inlined assets -->
  
<svg  viewBox="0 0 500 500" style="position:absolute; width:0; height:0;">
  <!--basic shape -->
  <defs>
    <circle id="circleEl" cx="250" cy="250" r="120" />
  </defs>

  <!-- symbol: loaded via use -->
  <symbol id="circles">
    <use href="#circleEl" style="fill:var(--fill1)" />
    <use href="#circleEl" y="120" style="fill:var(--fill2); scale:var(--scale); transform-origin:center;" />
  </symbol>

</svg>

See plunkr preview.

In the above example, we bind fill and scale properties via CSS variables using an inline styles attribute.
As always we avoid too specific element styling to allow customizations for each <use> instance

Related

Sign up to request clarification or add additional context in comments.

5 Comments

I actually wanted to avoid inline SVG. Example of different style application in SVG 2 Draft specification (5.5.3 chapter) has the following statement "In the SVG 1.1 style-cloning model, the specified style values would be cloned from the original element to the element instance." It makes me think that according to specification embedded styles should be applied and that maybe I just missed something in my code. Also I don't understand what strict content policies it can contradict.
Can you point out on info that support for loading an entire SVG file via use is relatively new? On MDN, I found info that support for loading entire SVG by omitting URL fragment is a new feature. But loading entire SVG file by adding id element to SVG root element seems existed from 2015.
Unfortunately (or maybe fortunately) the W3C doesn't write your browser's rendering engine. Sure, ideally each time an official spec is updated to an appropriate state all browser developers should try to implement these. To be fair: the specs are sometimes ambiguous – so vendors reject implementations or more often interpret the behaviors differently. Bear in mind the SVG 2 is in flux for ~10 years. Parts of it has been tossed around/renamed removed etc – so the W3C working groups are imho also to blame for not finalizing the spec versions at reasonable intervals.
Don't get me wrong reading the specs is great to get a profound understanding of SVG but the current v2 specs can sometimes be quite misleading for developers as they contain many features that are not implemented by any browser (e.g multiline text support). Fun fact: I just recently got informed by a SO member that loading entire SVGs are supported at least by Chromium and Firefox – it probably happened around end of 2024? I can however confirm it's relatively new. Unfortunately even databases like caniuse also can't describe these subtle feature additions – you need to test it
Also I don't understand what strict content policies it can contradict. I can't tell you either=) But when working with external use references you can definitely encounter quite a few issues like dropped styles or SMIL animations. However, these restrictions are probably for the best mainly to suppress something like embedded script - albeit these policies could have been more fine grained. I guess @Robert Longson may respond to your post once he has time to share some Firefox intel.
0

You can replicate this behavior in one inline SVG;
See the difference in FireFox and Chromium browsers.

From the W3 docs:

Two circles, one of which is a re-styled clone of the other. This file demonstrates one of the cases where the shadow-DOM style matching rules in SVG 2 have a different effect than the SVG 1.1 style cloning rules. The original circle on the left should have blue fill and green stroke.
In a conforming SVG 1.1 user agent (Chrome), the re-used circle on the right should have orange fill and green stroke.
In a conforming SVG 2 user agent (FireFox), the re-used circle should have orange fill and purple stroke. In all cases, the stroke should be partially transparent and 20 units wide, relative to a total circle diameter of 100 units.

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" viewBox="0 0 200 100">
  <title>Style inheritance and the use element</title>
  <style type="text/css">
    circle          { stroke-opacity: 0.7 }
    .special circle { stroke: green }
    use             { stroke: purple; fill: orange }
  </style>
  <g class="special" style="fill: blue">
     <circle id="c" cy="50" cx="50" r="40" stroke-width="20"/>
  </g>
  <use href="#c" x="100"/>
</svg>

Chrome:

FireFox:


Replace <use> usages with a native Web Component

Like herrstrietzel says, you can create SVG with a native Web Component.

  • MOVE your SVG to shadowDOM
  • query all <use>
  • replace with <circle>
  • update CSS for required styling
  • By applying shadowDOM you are totally save with using (scoped) ID values

NOTE: stacksnippets.net has access problems, SO Snippet might not run; try later

<script>
  customElements.define("svg-circles", class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => { // wait till lightDOM is parsed
        this.attachShadow({ mode: "open" }).append(...this.children); // MOVE initial SVG to shadowDOM
        let uses = Array.from(this.shadowRoot.querySelectorAll("use")); // process all <use> elements
        uses.forEach((use, index) => {
          let referenced = this.shadowRoot.querySelector(use.getAttribute("href")); // get orinal href="#circle"
          if (referenced) {
              let clone = referenced.cloneNode(true); // clone <circle>
              clone.id = "circle" + (index + 1);
              clone.setAttribute("cx", parseFloat(use.getAttribute("cx")));
              use.replaceWith(clone); // replace <use> with cloned <circle>
          }
        });
      });
    }
  });
</script>
<svg-circles>
  <svg height="100" viewBox="0 0 500 100">
    <style type="text/css">
      circle   { stroke-opacity: 0.7; stroke: green; fill: blue }
      #circle1 { fill: orange }
      #circle2 { stroke: rebeccapurple }
    </style>
    <circle id="circle" cx= "50"  cy="50" r="40" stroke-width="20" />
    <use href="#circle" cx="150" />
    <use href="#circle" cx="250" />
  </svg>
</svg-circles>

6 Comments

1. Before creating this post I've checked SVG 2 specification draft and specifically example provided in 5.5.3 chapter. 2. It doesn't replicate my case at all. It shows the difference in selectors weight/specificity logic applied in SVG 1.1 and SVG 2. It doesn't show no CSS applied, it shows that in one case one selector overrides the other, and that the complex selectors in certain condition could not match any selector. Try to save that code as svg file and reference it on the page using <svg><use> and you wouldn't see any colorful circles in FF. They all would be black - the default fill.
3. This example actually lead me to write this question on SO, because in the description is literally stated that even in the scope of SVG 1.1 which is current standard since 2011: "In the SVG 1.1 style-cloning model, the specified style values would be cloned from the original element to the element instance. " And thus I don't understand why Firefox not follow this.
Later I am gonna check deeper suggested approach of using Web Components instead of svg-use. But on the surface it doesn't look like a replacement. It looks like simply puting inline SVG would simpler than Web Components. With Web Components it looks like you have to inline SVG with JS. SVG-USE approach is the opposite attempt to use external svg file that would encapsulate image(s)/icon(s) with corresponding styles including animations and potentially customizable by CSS variables.
Yes, use would be great; but it just doesn't work cross-browser in this scenario. You can also write a Web Component that fetches your external SVG and loads it into shadowDOM; Here is the <load-file> Web Component I blogged about
Thanks for link. I am in the process of exploring Web Components.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.