--- { title: "Why is z-index not working?! - Explaining CSS Stacking Context", description: "", published: '2023-01-01T22:12:03.284Z', authors: ['crutchcorn'], tags: ['webdev', 'css', 'html'], attached: [], license: 'cc-by-4' } --- Dimensions are weird. While most web apps seem to focus on the `x` and `y` axis, representing a 2D plane that the user interacts with, there's actually a `z` axis that's often ignored. Some browsers, such as [Microsoft Edge, even provide a way to see a website blown up into a 3D view](https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/3d-view/): ![A view of Google in Microsoft Edge's 3D View debugger mode](./google_3d_view.png) While this is cool, by introducing a third dimension to our webpages, we introduces the ability for elements to overlap with one another. Managing overlapping issues using CSS is _tricky_. Sure, you have [`z-index`](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index), which many claim is an easy way to manage an element's `z` axis, but it seems so brittle and inconsistent! > This article doesn't expect you to have pre-existing `z-index` knowledge. If you're being sent this article to learn how `z-index` works, you're in the right place. For example, let's think about modals. Modals are UI elements that enable you to display information in a box that rests above the rest of your page's contents. This is what an example modal looks like from [Google Drive](https://drive.google.com/): ![A box resting above other elements of Google Drive showing "New folder" with a "Create" and "Cancel" button](./example_dialog.png) [Despite some some UX headaches modals can introduce into an app](https://modalzmodalzmodalz.com/), they're still a widely used UI element in many applications today. While building sufficiently useful modals can be a challenging task, a rudimentary modal can be completed even without JavaScript. Let's use some CSS and HTML in order to build a basic modal: ```html

This is some text, pretend it's an app back here

``` ![A modal that's focused in the foreground, with a semi-transparent black background that dims all other elements](./initial_modal.png) Tada! 🎉 Now we have a fairly basic modal to display whatever HTML we want inside. But let's say that we keep building out the page. As we do, we might, for example, want to have a `footer` beneath our main page's content. ```html

This is some text, pretend it's an app back here

``` At first glance, this might look like it's been successful, but let's take a look at the rendered output: ![The footer is on top of the modal, instead of beneath it](./partially_hidden_modal.png) Oh dear! Why is the footer rendered above the modal? Well, my friends, the modal is rendering under the footer due to something called "Painting Order". # What is painting order? While the concept of the "painting order" in the DOM is quite complex, here's the gist of it: Your browser is fed information from HTML and CSS in order to figure out what to show on screen. While we often think of this process as instantaneous, nothing in computer science truly is. **This process of showing HTML and CSS on the screen is called "painting" or "drawing" the screen.** Painting contents on the screen might sound straightforward at first, but think about what that entails: Given every bit of HTML and CSS, figure out where they belong and display it. There's a lot of naunce there; Nuance that's dictated by a strict set of rules. While we'll take a look into the specific rules of painting in a moment, let's start by taking a look at the following code example: ```html
Blue
Green
Purple
``` Here, we have three different boxes that overlap on one another. Given that they overlap, **which one do you think takes priority and, at least visually, is on top of the other boxes**?
No, really, guess! Stop reading, take a look at the code, and take a guess. 😊










Ready to see the answer?











OK, here it is: ![The three colored boxes are, in order from top to bottom: Purple, green, then blue.](./boxes_demo.png) The reason these colored boxes are in the order they're in is thanks to their respective "paint order". The browser walked through its rules of "what order should I paint things in" and settled on this order. While some CSS pros might assume that purple is seemingly on the top is [due to order in which the CSS is laid out, just like other CSS rules](https://wattenberger.com/blog/css-cascade#position), this isn't what's happening here. Notice how the purple box seemingly remains on "top" when we re-arrange the CSS rules: ```css #purple { background: #5f00b2; left: 150px; top: 150px; } #green { background: #007a70; left: 100px; top: 100px; } #blue { background: #0f2cbd; left: 50px; top: 50px; } ``` ![The three colored boxes remain in the same order from top to bottom: Purple, green, then blue.](./boxes_demo.png) > If changing the CSS order doesn't re-arrange the boxes and change the paint order, then what does? Well... # Re-arrange HTML Elements to Change the Painting Order Let's take the HTML we had before, and re-arrange it a bit: ```html
Purple
Green
Blue
``` Now if we look at the box order, we'll see... ![The box orders have flipped! Now, in order from top to bottom, it's: Blue, green, then purple.](./boxes_reverse_demo.png) Now our boxes have reversed their height order! This is because one of the deciding factors of an element's painting order is its relationship to other elements. # Positioned Elements Behave Differently Than Non-Positioned Elements > This is where things get confusing. Take your time with this chapter, it's okay to have to re-read this section multiple times. While we were using `absolute`ly positioned elements for a simple demo before, let's take a step back and change our elements to be positioned using `margin` instead: ```css
Purple
Green
Blue
``` Looks like a familiar output: ![The same exact three colored boxes in the order from top to bottom: Purple, green, then blue.](./boxes_demo.png) While working on styling, we wanted our `green` box to move to the left when you hover over it. This is straightforward enough to do [using CSS animations](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations), let's add it: ```css #green { background: #007a70; position: relative; left: 0px; transition: left 300ms ease-in-out; } #green:hover { left: 20px; } ``` While our green button now smoothly moves left when you hover over it, there's a new problem: The green box is now on top of the purple and blue boxes. ![The same colored boxes but green appears to be on top](./boxes_green_top.png) This is because browsers paint positioned elements before non-positioned elements. This means that our `relative` positioned element is painted first, and seems to takes priority in the `z` layer over non-positioned elements. # Understanding more rules of Painting Order While `relative` positioning is one way that you can tell the browser to paint an element first, it's far from the only way to do so. Here's a list of CSS rules that will change the order an element paints in, from the lowest priority to the highest priority: 1. The `background` of the following tags: `html`, `:root`, `body` 2. The `background` of the [stacking context root element](#stacking-contexts) > Come back to this at the end of the article, it won't make sense now. 3. Positioned elements with a negative `z-index` 4. Non-positioned elements 5. Elements with a `float` style applied without a `position` applied 6. Non-positioned `inline` elements 7. ??? 8. Positioned elements without a `z-index` applied, or with a `z-index` of `0`, as well as a few other rules 9. Elements with `z-index` of `1` or more 10. Depending on your browser, [`outline`](https://developer.mozilla.org/en-US/docs/Web/CSS/outline)s > While this includes [all of steps of painting order according to the CSS specification](https://www.w3.org/TR/CSS22/zindex.html), this is a non-comprehensive list in order to keep the list readable. So, if we have the following HTML: ```html
Slate
Yellow
Lime
Green
Cyan
``` We would see, from top to bottom: - A `slate` colored box - A `yellow` colored box - A `lime` colored box - A `green` colored box - The `container`'s background - A `cyan` colored box ![The boxes in order as mentioned above](./boxes_stacked_order.png) All of these rules are superseded by the order of the elements within the HTML, as we learned before. For example, with the following HTML: ```html
Slate
Yellow
Lime
Cyan
``` You would see the following order of elements: - Lime - Slate - Cyan - Yellow ![A square of blocks demonstrating the order as laid out above](./blocks_square_html_order.png) This is because the `lime` and `slate` take painting priority over `yellow` and `cyan` thanks to their `relative` positioning, but are still in HTML order within the same `z` level priority and within the same stacking context. # Creating Stacking Contexts {#stacking-contexts} > "Welp, that's enough reading in the book today" You think to yourself. You go lay down and get some sleep. In your dreams, you can still hear the book speaking to you: > [...] are still in HTML order within the same `z` level priority and within the same stacking context > [...] within the same stacking context The book repeats itself: > [...] within the same stacking context You wake up, realize that you don't yet know what that sentence means, and think to yourself: > There's no way this gets even more complicated. Unfortunately, it does. ---- At its heart, a stacking context is a group that you can move multiple items up or down the `z` axis at the same time. Take the following HTML: ```html
Slate
Yellow
Lime
Cyan
``` What order do you think the `box`es are going to be in? ![Colored boxes in the order as described below](./new_stacking_context_before.png) The answer is: - Slate - Lime - Cyan - Yellow This is because, despite the parent `top-container` having `position: relative`, the `box`es are still within the same stacking context. This stacking context follows the same ordering rules as outlined before, which means that the positioned `slate` and `lime` `box`es take `z` priority over `cyan` and `yellow`. Ready for the twist? Let's add `z-index` to our `top-container`: ```html
Slate
Yellow
Lime
Cyan
``` Now what order do you think they'll be in? ![The colored boxes reordered in the manner outlined below](./new_stacking_context_after.png) - Slate - Yellow - Lime - Cyan This is because, in reality, what we're ordering here is not the `box`es, but instead is the `top-container` and `bottom-container` `div`s, **then** the `box`es, like so: - `top-container` - `slate` - `yellow` - `bottom-container` - `lime` - `cyan` The reason this only occurred when we added a `z-index` to `top-container` is because that's when a new stacking context was created. When that context was created, we raised it to a higher `z` axis due to the same ordering rules as before. > Remember, a stacking context is a grouping of elements that move together as a collection when the parent's `z` axis location is changed. Stacking Contexts are created when: - `z-index` is applied to a positioned element - `z-index` is applied to a child of a `grid` or `flex` element - Element with an [`opacity`](https://developer.mozilla.org/en-US/docs/Web/CSS/opacity) less than `1` - Element with any of the following properties: - [`transform`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform) - [`filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter) - [`backdrop-filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter) - [`perspective`](https://developer.mozilla.org/en-US/docs/Web/CSS/perspective) - [`clip-path`](https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path) - [`mask`](https://developer.mozilla.org/en-US/docs/Web/CSS/mask) / [`mask-image`](https://developer.mozilla.org/en-US/docs/Web/CSS/mask-image) / [`mask-border`](https://developer.mozilla.org/en-US/docs/Web/CSS/mask-border) > This list is non-exhaustive, but contains most of the highlights of when a stacking context is created. It's worth mentioning that if a stacking context is created, then the element that created said stacking context is treated with priority `z` axis ordering. For example, if you have: ```html
Absolute
Opacity
``` Then it will show "Absolute" above "Opacity", thanks to the order of the HTML sequence; this is all despite positioned elements typically being prioritized above HTML sequencing. If we remove the `opacity: 0.99` from the `"Opacity"` `div`, then `"Absolute`" will be on top. # Stacking Stacking Contexts While the previous sections have been head scratchers, let's dive into mind melting territory: You can contain stacking contexts within other stacking contexts. 🤯 // TODO: Write # The Problem with Stacking Contexts // TODO: Explain that `z-index` cannot escape > If you want to learn more about the "stacking context", I'd suggest reading through the following resources: > > - [Stacking elements - CSS z-index and stacking context explained - NetGen](https://netgen.io/blog/stacking-elements-css-z-index-and-stacking-context-explained) > - [The stacking context - MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context) > - [What The Heck, z-index?? - Josh W Comeau](https://www.joshwcomeau.com/css/stacking-contexts/) > - [What No One Told You About Z-Index - Philip Walton](https://philipwalton.com/articles/what-no-one-told-you-about-z-index/) > - [Appendix E. Elaborate description of Stacking Contexts - W3C](https://www.w3.org/TR/CSS2/zindex.html)