SVG z-index: how to bring a shape to the front

SVG shapes ignore z-index: they are painted in the order they are written. How to bring one to the front by moving it, with JavaScript, or with a use element.

Published

There is no z-index inside an SVG. Shapes are painted in the order they appear in the code, each one over the ones before it, so the last element written is the one on top. To bring a shape to the front, move it later in the markup. Setting z-index on a <circle> or a <g> does nothing.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 100">
  <circle cx="50" cy="50" r="35" fill="teal"/>
  <circle cx="80" cy="50" r="35" fill="gold"/>
  <circle cx="110" cy="50" r="35" fill="crimson"/>
</svg>

The teal circle is painted first and ends up at the back. The red one is painted last and covers part of the other two. Swap the first and last lines and the teal circle comes to the front.

The SVG specification calls this the painters model: each element is painted on to the picture so far, as a painter adds one coat over another.

Ways to change the order

AimHow
Fixed order in a fileMove the element’s line later (front) or earlier (back)
Bring to front with scriptparent.appendChild(el)
Send to back with scriptparent.prepend(el)
Put one element just above anotherother.after(el)
Show a shape on top without moving itA <use href="#id"/> at the end
Order by a number, like z-indexSort the elements by a data-z attribute and append them again

Bring to front with JavaScript

Appending an element that is already in the document moves it. It is not copied. So appending a shape to its own parent makes it the last child, which is the front:

<svg viewBox="0 0 160 100" width="320">
  <circle cx="50" cy="50" r="35" fill="teal"/>
  <circle cx="80" cy="50" r="35" fill="gold"/>
  <circle cx="110" cy="50" r="35" fill="crimson"/>
</svg>
<script>
  document.querySelectorAll('circle').forEach((circle) => {
    circle.addEventListener('click', () => circle.parentNode.appendChild(circle));
  });
</script>

Click any circle and it jumps to the front. circle.parentNode.prepend(circle) sends it to the back. In D3, selection.raise() and selection.lower() do exactly these two things.

Be careful doing this on hover. In Chrome, moving a shape in the document while the pointer is over it makes mouseenter fire again, so the handler runs many times over. For hover effects the <use> method below is steadier.

Show a copy on top with use

A <use> element draws a copy of another element at the place where the <use> is written. Put one at the end of the SVG and whatever it points at is drawn again on top of everything:

<svg viewBox="0 0 160 100" width="320">
  <circle id="a" cx="50" cy="50" r="35" fill="teal"/>
  <circle id="b" cx="80" cy="50" r="35" fill="gold"/>
  <circle id="c" cx="110" cy="50" r="35" fill="crimson"/>
  <use id="top" href="#a"/>
</svg>

The teal circle now appears in front, though its own line has not moved. To raise a different shape, change one attribute:

document.querySelector('#top').setAttribute('href', '#b');

The original is still painted underneath, which does not show when the copy covers it exactly. SVG g and use explains <use>.

To raise whichever shape the pointer is over, start with an empty <use id="top" pointer-events="none"/> as the last element and point it at the hovered shape:

const copy = document.querySelector('#top');
document.querySelectorAll('circle').forEach((circle) => {
  circle.addEventListener('mouseenter', () => copy.setAttribute('href', '#' + circle.id));
  circle.addEventListener('mouseleave', () => copy.removeAttribute('href'));
});

pointer-events="none" keeps the copy from taking the hover away from the shape underneath, and nothing moves in the document.

Order by number

If the data already carries a stacking number, write it on each element as data-z and sort:

const svg = document.querySelector('svg');
[...svg.children]
  .sort((a, b) => (a.dataset.z ?? 0) - (b.dataset.z ?? 0))
  .forEach((el) => svg.appendChild(el));

Elements with a higher data-z end up later and so in front. Elements with the same number keep their order. Run it again whenever the numbers change.

In React, Vue and similar libraries, do not move the elements by hand. Sort the array before rendering it, so that the item to show on top is the last one drawn.

Groups move as one

A <g> is painted as a unit, at the group’s place in the order. Everything inside it is painted before the next sibling of the group starts.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 100">
  <g>
    <circle cx="50" cy="50" r="35" fill="teal"/>
    <circle cx="110" cy="50" r="35" fill="crimson"/>
  </g>
  <circle cx="80" cy="50" r="35" fill="gold"/>
</svg>

The gold circle covers both of the others, because it comes after the whole group. No change inside the group can lift the red circle above the gold one. Either move the gold circle before the group, or take the red circle out of the group and put it last.

This is the usual reason a shape “will not come to the front”: it is the last child of its group, but the group itself is early in the file.

Inside one shape: fill, stroke, markers

A single shape is painted in three steps: the fill, then the stroke over it, then any markers. The paint-order attribute changes that. paint-order="stroke" paints the stroke first, so the fill covers the inner half of the line:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 60">
  <text x="10" y="45" font-size="40" font-weight="bold" fill="gold"
    stroke="teal" stroke-width="6" paint-order="stroke">Front</text>
</svg>

The outline sits behind the letters and does not eat into them. Fill and stroke has more on outlines.

Where z-index does work

The <svg> element as a whole is a box in the HTML page, like an <img> or a <div>. CSS can stack that box against the rest of the page:

<div style="position: relative">
  <svg viewBox="0 0 10 10" width="100" style="position: absolute; z-index: 2">
    <rect width="10" height="10" fill="teal"/>
  </svg>
  <svg viewBox="0 0 10 10" width="100" style="position: absolute; left: 50px; z-index: 1">
    <rect width="10" height="10" fill="gold"/>
  </svg>
</div>

The teal square is on top although it comes first, because its <svg> has the higher z-index. This works between whole SVGs and other page elements, never between shapes inside one SVG.

Clicks follow the same order

The shape on top is the one that receives a click or a hover. A fully transparent shape lying over another still takes the click if it has a fill. One hidden with visibility="hidden" does not. To let the pointer through to what is underneath, set pointer-events="none" on the shape on top:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="40" fill="teal"/>
  <text x="50" y="56" text-anchor="middle" fill="white" pointer-events="none">Label</text>
</svg>

A click on the label now reaches the circle.

Common problems

What you seeCauseFix
z-index in CSS has no effect on a shapeSVG paints in document orderMove the element later in the markup
A shape is last in its group but still hiddenA later group or shape covers the whole groupMove the group, or take the shape out of it
A label is hidden behind its own shapeThe text is written before the shapeWrite the text after the shape
A hover handler that raises a shape runs again and againThe shape is being moved in the document under the pointerDraw a <use> copy on top
Clicks land on the wrong shapeSomething lies over itpointer-events="none" on the upper shape

Questions

Will SVG ever get z-index? A z-index property was added to the drafts of SVG 2 and then taken out again, deferred to a future version of SVG. Document order is the rule to rely on.

How do I do this in a vector editor? Commands named raise and lower, or bring to front and send to back, change the order of the objects in the file. They do the same thing as moving the lines by hand.

Does the order matter for cutting and plotting? The order decides what covers what in the picture. Whether a cutting or plotting program also works through the paths in file order depends on the program, so check its own settings.

How can I see the order? Open the file in the SVG viewer and read its code box from top to bottom: the top of the code is the back of the picture. Move a line there and the picture follows.