BTrem

clash of the id attributes in svg

CSS-Tricks has an article about duplicate titles and id attributes in svg. The article discusses the problems that might arise when an author is relying on title elements and id attributes for ARIA accessibility. But there's another, more fundamental problem if you insert svg code directly in an html document and end up with with duplicate id attributes. A problem that could bork how the browser renders the svg.

Let's begin with a simple example of an html page with svg elements:

<body>
<svg viewBox="0 0 300 200">
<rect width="300" height="200" fill="blue"/>
</svg>
<svg viewBox="0 0 300 200">
<rect width="300" height="200" fill="red"/>
</svg>
</body>

Such a page will display two rectangles, one blue, one red. Suppose you want the color to shift, from one color on the left side to another on the right. For that, you need to create a separate gradient element, give that element an id attribute, and reference it in the svg rectangle with a fragment url. Here's an example:

<body>
<svg viewBox="0 0 300 200">
<linearGradient id="myfill">
<stop offset="0" stop-color="blue"/>
<stop offset="1" stop-color="green"/>
</linearGradient>
<rect width="300" height="200" fill="url(#myfill)"/>
</svg>
<svg viewBox="0 0 300 200">
<linearGradient id="myfill">
<stop offset="0" stop-color="red"/>
<stop offset="1" stop-color="yellow"/>
</linearGradient>
<rect width="300" height="200" fill="url(#myfill)"/>
</svg>
</body>

You might expect this page to show two rectangles, one blue-to-green, the other red-to-yellow. But that's not what it produces. Take a look at a live version of this svg gradient example. Although the svg elements have different <linearGradient/>s, the rectangles are identical: blue-to-green in Firefox 86 and Chrome 88 on Mac OSX; red-to-yellow in Safari 13.1.2 on Mac OSX as well as Safari, Firefox, and Chrome on iOS 9.3.6 (yes, my iphone is really old).

what's going on?

The sharp eye will notice that there are two identical id attributes in the example code, and that's not allowed. In an html document, each id must be unique, so the browsers have to do a little error correction. In Firefox and Chrome on OSX, it appears to go like this:

  1. get the rectangle's fill attribute, which in the example code is url(#myfill)
  2. retrieve the fragment url #myfill, that is, search the example document for an element whose id="myfill"
  3. find and use the first instance of <linearGradient id="myfill"/> (and ignore the second)
  4. render the rectangle blue-to-green as defined in that first <linearGradient/> element

Note that Firefox and Chrome follow the same steps for each rectangle, resulting in both rectangles having a blue-to-green color.

Safari OSX and Safari/Firefox/Chrome iOS follow the same steps 1 and 2 as listed above. Then they

  1. find and use the second instance of <linearGradient id="myfill"/> (and ignore the first)
  2. render the rectangle red-to-yellow as defined in that second <linearGradient/> element.

As before, these browsers follow the same steps for each rectangle, so each rectangle ends up with the same color, only in this case it's red-to-yellow.

solution

There is no way apply a gradient without using the id attribute:

You must give the gradient an id attribute; otherwise it can't be referenced by other elements inside the file. Gradients are defined in a defs section as opposed to on a shape itself....

But you can change the id on the second <linearGradient/> element so that the browser can differentiate between them. And since documents are not supposed to contain two or more matching id attributes, that's the right thing to do anyways.

You could also save the svg elements as separate .svg files — making the clashing id problem moot — and use <img> elements to put them in your web page.

real world encounter of clashing ids

In , I published two versions of the microformats logo in svg format (using <img> elements to put them on the page). Each version of the logo was in its own .svg file, and, for no particular reason, I used the same id attribute for the <linearGradient/> element in both files.

A couple of weeks later, I decided to remove the <img> elements and put the svg markup directly in the page instead. When I did, kaplooey! I got two identical logos. And since I rarely use the id attribute, I didn't even think about clashing ids. After half-an-hour or so of head scratching, I realized my mistake and changed the id and fill attributes of the orange logo. Problem solved.