--- { title: "Why is z-index not working?! - Explaining CSS Stacking Context", description: "z-index is a CSS property that helps control the z-axis height of an element, but it often doesn't seem to work. Why is that? How does it REALLY work?", published: '2022-10-06T22: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 web pages, we introduce the ability for elements to overlap with one another. Managing overlapping issues using CSS is _tricky_. Sure, you have a [`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 UX headaches that 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 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 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 them. There are a lot of nuances 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 with 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 [due to the 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 take 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' `background` 5. Elements with a `float` style applied without a `position` applied 6. Non-positioned `inline` elements 7. The text contents of non-positioned, non-floating elements, as well as a few other rules 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 a `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 the steps of painting order according to the CSS specification](https://www.w3.org/TR/CSS22/zindex.html), those steps may have more sub-steps. Overall, this list is non-comprehensive 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. Instead, what we are ordering is the `top-container` and `bottom-container` `div`s, **then** the `box`es, like so: - `top-container` - `slate` - `yellow` - `bottom-container` - `lime` - `cyan` This only occurred when we added a `z-index` to `top-container` 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) of 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. # Another Modal Stacking Context Example Let's take a look at what we've learned thus far. We know from the previous section that `position: relative` and a `z-index` greater than `1` should create a stacking context. Given this knowledge, what do we expect the following code to render? ```html
Header
``` No major surprises here; it renders a greenish rectangle positioned atop a blue rectangle with the text of "Header" and "Footer", respectively. ![Two rectangles shown in the same manner as described in the last sentence.](./header_footer_stacked_context.png) Now, let's say that we want to show a modal on top of the footer to show the user information they might think is relevant. Now, what do you think happens if we add in said modal using `position: absolute; z-index: 99` within the `
`? ```html
Header
``` You might be surprised to find that nothing changes - it seemingly doesn't show the modal anywhere and instead shows the same two "Header" and "Footer" elements as before: ![The same two rectangles as before](./header_footer_stacked_context.png) However, if we change the `footer` code to the following: ```html ``` > The only thing we changed was `z-index` from `1` to `0` Then suddenly, we see the modal above the `Footer` as expected: ![Two green rectangles with "header" and "modal in the header" repectively](./header_footer_stacked_context_modal.png) > Why is this happening? Well, this occurs because we create a stacking context inside of `header ` when we do `position: relative; z-index: 1`. This, in turn, places the containing `id="modal"` `div` inside of the stacking context of `header`. As a result, `id="modal"`'s `z-index` does not apply outside of the stacking context, and the group of elements under `header` is treated as `z-index: 1` from the perspective of the `footer`. Here's a diagram showing what I mean: ![The header and the modal div are in the same stacking context with the z-index of 1](./css_stacking_context_level_1.svg) You'll see that both `
` and `