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:
As you can see, there is a canvas in the design, and above it, a panel with buttons:
- The buttons need to have the same gap between each other.
- 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" />
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" />
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:
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:
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
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:
- Traverse the root node
- Traverse sub-trees from left to right
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)