DEV Community

Cover image for Stacking Contexts and why you should go deep on CSS (a personal journey)
Angus Bower-Brown
Angus Bower-Brown

Posted on • Updated on

Stacking Contexts and why you should go deep on CSS (a personal journey)

I'm reaching the end of a software training and am reflecting on my progress. This blog is a little more focused on the abstracts of learning css and my own personal learning journey. For a more technical view of stacking contexts, this blog is great


Josh Comeuax, the CSS wizard, often starts his talks with a question:

Is CSS a hard language?

It's intentionally provocative, because, in many ways CSS is relatively easy to understand. Non-programmers could look at a declaration like bottom-border: 1px; or float: right; and have a rough idea of what it's going to do.

I'm willing to bet though, that a large number of web devs would answer 'yes' (or at least, 'it can be') to this question. So, why is that?

In my experience, CSS is rarely taught by itself in web-development courses. By the time we hit a deeper language or framework, there's so much to learn when it comes to building web apps, that CSS is kind of left behind. We often end up relying on cobbled together methods we picked up in our very first web projects.

I felt this really keenly with a messaging project I worked on this spring. It felt like what was holding me back as a developer was:

  • The way I was dealing with/thinking about asynchronous data

  • My styling

For the former, I picked up a great tool that I'll blog about another time.

For the latter, I thought I'd write a bit about my history with css; how learning more about it gave me the tools to solve problems that had bugged me for a long time. Hopefully it can give you a clue where to look if you're dealing with a similar problem!


Why can CSS be hard to deal with?

1) No error messages

  • CSS won't throw an error if it thinks your style is ugly (thankfully)

  • It simply applies the styles you've given it and we behold the results.

  • The only 'error' you can really trigger in CSS is a bad declaration (a spelling/syntax error, a non-sensical declaration one like position: darkblue;)

  • Even then, in those situations, css simply doesn't apply that style (and maybe the following one) and moves on to the next valid one it can find which is not particularly useful in a debugging situation!

2) New keyword, new rules

  • All we have to do is put down a declaration like display: grid; or position: fixed;, and we're suddenly in a completely new CSS world, with a new layout algorithms.

  • This means we have a whole new set of keywords to handle our elements AND a whole other set of keywords can become totally useless. e.g using float: right; on an element that's absolutely positioned.

If something's not working, it can be incredibly hard to figure out why. CSS's underlying rules can be pretty opaque, but taking the time to figure out what's going on under the hood can often give us a clue as to what the problem is.

Let me give a personal example...

An annoying bug

The first website I ever designed was for a project given to me by my software engineering course, about a year ago. I had to put together a personal website using HTML, CSS and very basic JavaScript.

It looked like this:

My first personal website

Looking back, I'm quite happy with how it came out. To be honest, it's a lot cleaner than a lot of websites that were to follow 😅


There was one problem that really bugged me though...

I'd followed some tutorials and made some nice hover effects for some links:

Link animations triggering when hovered by mouse

But some wouldn't trigger when hovered.

Link hover animations not triggering when hovered over by mouse

What's worse, they weren't even clickable.

My styling wasn't just affecting my site's appearance, it was affecting its functionality.

It's easy to think of styling and functionality as separate, but it's important to remember that styling is an essential part of how users interact with our sites, both aesthetically and functionally.

This was an annoying problem and I put everything into trying to solve it.

The problem

I managed to figure out why the links weren't triggering, by looking at the browsers tools:
Browser's tools showing the subordinate element's margin covering the non-hovering links

  • The carousel below the biography section of my site (where the links weren't working) had a top margin that was covering the biography

  • When a user clicked or hovered over my links, they weren't interacting with the links at all, but with the containing div of that carousel

The solution?

Folks, I'm willing to bet that all of you reading this will have experienced at least one debugging session that stretched into the ridiculous. This was my very first one.

Despite knowing what the problem was, I had no idea how to solve it.

Me and my instructor tried a variety of things:z-indexes, different display modes, different positioning modes- we couldn't get the links to animate or function.

Gradually, new requirements and projects arose and I (reluctantly) moved on with my life, but the amount of time I spent on this issue and the fact that I wasn't able to solve it really gnawed at me.

Meme of someone estranged at a lively party, caption:


What bugged me distinctly was how my z-indexes just didn't work.

  • I cranked them up to ridiculously high and low numbers respectively

  • I knew that z-indexes are aspects of the positioned layout mode, so I made sure my elements were all positioned in some way

  • None of this seemed to matter or make any change to my rendering problem

It wasn't until I decided to take a deeper dive into CSS, with some courses and personal research, before I understood what was happening. It was pretty satisfying (if petty) to go back and solve the problem a year later 😂

So what was going on? What aspect of CSS was I missing?

A Puzzle

Below's a representation of the code of the two elements involved here.

Ask yourself, would the div with a class of "red", or the div with a class of "blue" render on top in the DOM:

<style>
  div {
    position: relative;
  }

  .container {
    z-index: 3;
  }

  .blue {
    z-index: 1;
  }

  .red {
    z-index: 2;
  }
</style>

<div class="container">
  <div class="blue"></div>
</div>

<div class="red"></div>

Enter fullscreen mode Exit fullscreen mode

If you guessed blue would appear on top, you guessed right! But why? It's z-index is lower than red? What's going on here?

A DOM where the blue square is on top of the red

Stacking Contexts

The big piece of information that I think most beginners are missing when it comes to understanding z index is this:

z-index values aren't global.

Our browser doesn't look through the style sheet and render all the elements with a z-index of 1 at one layer of the DOM and all the elements with a z-index of 2 at another.

Instead, they render elements based on their z-index AND the stacking context that contains them.

A 'stacking context' is a little similar to the idea of a functional scope.

Just as when we create a new function, we create a new scope, when we make give an element a positioned layout and a z-index, we create a new stacking context.

Is that the only way to create a stacking context? Absolutely not.

Creating a stacking context

There are a variety of ways we can create a stacking context.

They include changing an element's:

  • opacity
  • mix-blend-mode
  • z-index (not just in positioned layout, but in the flex and grid display modes too)

It's entirely possible to create a stacking context by accident! But what exactly happens when we do?

Using stacking contexts

When we create a stacking context (most commonly when we combine a z-index with a positioned layout), we create an insular world of z-indexes.

Z-indexes defined within that stacking context will only be compared to z-indexes also within that stacking context.

So, in our example:

<style>
  div {
    position: relative;
  }

  .container {
    z-index: 3;
  }

  .blue {
    z-index: 1;
  }

  .red {
    z-index: 2;
  }
</style>

<div class="container">
  <div class="blue"></div>
</div>

<div class="red"></div>

Enter fullscreen mode Exit fullscreen mode

The red element's z-index of 2 is only compared with the container element's z-index of 3. The blue element is isolated within the stacking context created by the container element.

As it's z-index is larger, the container element (and therefore the blue element) will be rendered on top of the the red:

A DOM where the blue square is on top of the red

The blue div's z-index is totally irrelevant when it comes to discussing the order of the red and container elements- will only get compared to elements within the same stacking context.

Say we wanted it to render behind the red box, we could try and give it a crazy negative z-index:

<style>
  div {
    position: relative;
  }

  .container {
    z-index: 3;
  }

  .blue {
    z-index: -9999;
  }

  .red {
    z-index: 2;
  }
</style>

<div class="container">
  <div class="blue"></div>
</div>

<div class="red"></div>

Enter fullscreen mode Exit fullscreen mode

It would render in exactly the same way:

The same render order of blue on top of red

(I don't know about you, but that crazy high z-index number looks painfully familiar!)

The only thing our red div's z-index is being compared to is other z-index's of elements in that stacking context

The only way we can get the blue to render on top of the red is by changing the z-index in that top-level stacking context, ie. of the container div:

<style>
  div {
    position: relative;
  }

  .container {
    z-index: 1;
  }

  .blue {
    z-index: 3;
  }

  .red {
    z-index: 2;
  }
</style>

<div class="container">
  <div class="blue"></div>
</div>

<div class="red"></div>
Enter fullscreen mode Exit fullscreen mode

Now the situations have reversed. Despite the lower z-index, our red div is now on top:

red element on top of blue


So, that's a brief summary of stacking contexts. Is there a takeaway here?

For me, it's this:

Be honest about what frustrates you and explore it

The point of this blog wasn't really to explore stacking contexts in any great depth but to emphasise that if we're regularly frustrated by the same problem in our projects, it can be really rewarding to prioritise those things in our learning journey.

Being frustrated about how clumsy my styling felt in my personal projects led me to explore the mechanics of CSS a bit further. That lead me to a mental model that let me comment a single line of code:
An uncommented position: absolute declaration

And solve a problem that had been bugging me for a year!
the link animations animating on hover

Maybe being petty is a good learning process? 🤔

Top comments (0)