DEV Community

0ro
0ro

Posted on • Updated on • Originally published at 0ro.github.io

Z-Index Trick You Might Not Know 🚀

In one of the projects where I recently worked, we had a pretty trivial task: Add a panel of buttons for our canvas editor. The design in Figma for this panel looked like this:

Figma design

As you can see, there is a canvas in the design, and above it, a panel with buttons:

  1. The buttons need to have the same gap between each other.
  2. And you should be able to draw behind the buttons.

How to achieve it?

<button>Button</button>
<button>Button</button>
<button>Button</button>
<canvas id="canvas" />
Enter fullscreen mode Exit fullscreen mode

So the first option that will satisfy our requirements is to create all buttons, add them position absolute property, then do some math and add left and top properties to every button. That's crazy, right? Especially if you have dynamic content inside of your buttons.

What is another approach?

<div class="panel">
  <button>Button</button>
  <button>Button</button>
  <button>Button</button>
</div>
<canvas id="canvas" />
Enter fullscreen mode Exit fullscreen mode

Let's see the problem from another perspective and try to redefine our task. To make a panel with buttons and make them respect each other, it's better to wrap them in a container. With flex or grid properties, we will have a desirable behavior for its children. Then we need to calculate the position for that container. You can see the result in the next image:

Panel above canvas

But what about our second requirement? We already can't draw in space between buttons. Even if we make our wrapper totally transparent, the click will be caught by this panel, and the canvas will not see it.

Ideally, we need to have something like this:

Canvas between panel and buttons

Is it even possible? To somehow sneak between your sibling (panel) and its children (buttons) and achieve that rendering order?

Yes, it is. And I'll tell you how.

A bit of theory

Let's say we set a position absolute property to our panel element in CSS. What do you think will happen with its rendering order on the page?

According to MDN:

When the z-index property is not specified on any element, elements are stacked in the following order (from bottom to top):

  • The background and borders of the root element.
  • Descendant non-positioned elements, in order of appearance in the HTML.
  • Descendant positioned elements, in order of appearance in the HTML.

So, because our panel is already a positioned element, it will be on the top of our canvas. If we set position: absolute to the canvas, then the canvas will be on the top because it appears after the panel element in HTML.

But what if we set z-index: 1 to our buttons? Logically, it seems that they should start counting their index from their parent (panel) and still be under the canvas, but they will be on top of the canvas!!!, and our initial task is done.

You can check it here in this interactive example
But how does it work?

Explanation

The trick is, if you don't set the z-index property to your position absolute element, this element doesn't create a stacking context. We didn't set z-index to panel and the ordering of render for our elements can be represented like this:

root element // 0
  panel // 1
    button // 3
    button // 4
    button // 5
  canvas // 2
Enter fullscreen mode Exit fullscreen mode

The number at the line of the element is the order of rendering. The order is defined as preorder depth-first traversal of the rendering tree. Pre-order traversal means:

  1. Traverse the root node
  2. Traverse sub-trees from left to right

Order tree

The more detailed information about ordering and stacking context, read in CSS spec

Conclusion 🚀

In conclusion, understanding the intricacies of z-index and how it interacts with the positioning of elements can lead to clever solutions, as demonstrated in my example. Remember that an element with position: absolute without z-index doesn't create a stacking context.

Top comments (0)