The original address: www.joshwcomeau.com/css/stackin…

By Josh Comeau

Unauthorized reproduction is prohibited.

Explores CSS cascading context, the number one misleading mechanism in CSS.

In CSS, we all know that z-index can be used to control the HTML hierarchy. Elements with larger exponentials will rank at the top of the page:

<style>
  .box {
    position: relative;
    width: 50px;
    height: 50px;
    border: 3px solid;
    background: silver;
  }
  .first.box {
    z-index: 2;
  }
  .second.box {
    z-index: 1;
    margin-top: -20px;
    margin-left: 20px;
  }
</style>

<div class="first box"></div>
<div class="second box"></div>
Copy the code

Since.first.box has a larger z-index value than.second.box, it is shown first. If we remove the z-index declaration from the code, then.first.box will be blocked by.second.box.

But it’s often not that simple. Sometimes a big Z-index is not invincible. Let’s see what happened to the code xiao Ming wrote!

<style>
  header {
    position: relative;
    z-index: 2;
  }
  .tooltip {
    position: absolute;
    z-index: 999999;
  }
  main {
    position: relative;
    z-index: 1;
  }
</style>

<header>
  My Cool Site
</header>
<main>
  <div class="tooltip">
    A tooltip
  </div>
  <p>Some main content</p>
</main>
Copy the code

Tooltip z-index: 999999 is greater than header z-index: 2. But why is the header still on top?

Before unraveling this mystery, we need to learn a knowledge point called Stacking Contexts. This is a bit of a puzzle, but it’s the most basic mechanism of CSS. In the rest of this article, what are they? How they work, and how we can use them to our advantage in our code.

This article is intended for: All the front-end development fears that z-Index once dominated.

Layers and Groups

If you’ve ever used an image editing software like Photoshop or Figma, you should be familiar with the concept of hierarchy:

The image below has 3 layers of different canvases like a pancake. On the lowest level is a photo of a kitten. On top of the photo are 2 layers of silly detail, and the final result is a bearded angel kitten!

In Photoshop, we can combine these layers:

Just like folders, a group allows us to combine a series of levels. Hierarchies between combinations cannot be mixed together. All dog hierarchies are built on top of cat hierarchies.

When we export the final combination, we don’t see cats at all, because all the layers are covered by cats:

In the same way, CSS hierarchies operate in much the same way as PS: elements are combined in stacking Contexts. When we give an element a z-index, then that value will only compete with other elements in the same context. Z-index is not global.

By default, a simple HTML text has only one context, which includes all the nodes. However, we can add more context!

There are many ways to create a context, but the most common way is by combining two declarations, position and Z-index:

.some-element {
  position: relative;
  z-index: 1;
}
Copy the code

When two declarations are put together, a secret switch is turned on: we create a new context, element. Some-element and its children will be divided into a group.

Let’s review the previous example:

<style>
  header {
    position: relative;
    z-index: 2;
  }
  .tooltip {
    position: absolute;
    z-index: 999999;
  }
  main {
    position: relative;
    z-index: 1;
  }
</style>
<header>
  My Cool Site
</header>
<main>
  <div class="tooltip">
    A tooltip
  </div>
  <p>Some main content</p>
</main>
Copy the code

We can first draw the context structure of this code:

Although the z-index of the.tooltip element is 999999, that value is only valid in the main tag. The z-index only controls whether the.tooltip is displayed above or below the p TAB.

Looking at the root context, we can compare the location of the header and main tags. Because the z-index of the main tag is smaller than the header. So main and its children are shown below the header.

Modify the example above

So how do we modify Xiaoming’s code so that tooltip is displayed on top of the header? We don’t need to add z-index to the main tag at all:

The main tag without z-index does not create the context. Now we look at the context structure of the code and it will look like this:

Now, because the header and tooltip elements are in the same context, their Z-index changes are pitting against each other to determine the winner!

Note: We are not talking about parent-child relationships of those elements here. No matter how complex a Tooltip is nested with other elements, browsers are only concerned with the cascading context.

Break the rules

In the example of changing the code above, we just removed the z-index from the main tag, because the z-index didn’t work at all. But what if the main tag really needs z-index to create a context? How do we do that without removing the z-index?

According to the rules of CSS, unfortunately there is no other CSS method to achieve the desired effect: elements in one context should never be compared to other contexts.

Fortunately, there are other ways to achieve that effect, and it takes a little ingenuity.

We can move the Tooltip out of the main tag, underneath the body tag, and use CSS positioning to make the tooltip look like a child of the header.

More ways to create cascading contexts

We’ve already seen how to create context by combining Relative Absolute and Z-index methods, but that’s not the only way! We can also do this by:

  • Set opacity to less than 1
  • Set position to fixed or sticky (no need to provide z-index in this case)
  • Set mix-blad-mode to multiply, hard-light, and difference (normal not 🙅)
  • Add z-index to a container with display: flex or display: Grid
  • Use Transform, Filter, clip-path, or Perpective
  • Set will-change to opacity or Transform
  • Create context directly with Isolation: Isolation (use of this in a moment!)
  • See the MDN implementation list for more details

In this example we use will-change to create a context for the main tag, so we still get the same result:

Common misconceptions about z-index

We already know that in order for z-index to work, we need to go ahead and set the position to relative and absolute, right?

That’s not quite true. Let’s look at this code again:

The second box is assigned z-index and is displayed above the other boxes. But we can’t see where position is declared, right!

For the most part, z-index only works on elements that have position (relative/absolute). In the flexbox layout, the Z-index of flex child elements works even if position is static

Let’s get this straight again.

Here’s a weird thing we might want to think about a little more.

In the previous Photoshop metaphor, we can distinguish between the concept of grouping and the concept of hierarchy: all visual elements are hierarchics, and groups are just containers to help organize hierarchics.

In the browser, the concept is a little fuzzy. All elements that use z-index create the context.

In general, when we decide to use z-index, we simply want to change the location of that element in the current parent context. We don’t want to create a context on that element! We need to think a little bit more about this.

When a context is created, it flattens all child elements. With all the child elements laid out clearly, we’ve essentially locked them inside.

We shouldn’t just think of z-index as a tool for changing the order of elements, we should also think of it as a way to wrap (combine) its child elements. Z-index does not take effect if the group is not generated.

As we’ve seen in previous examples, context can sometimes create subtle, difficult-to-debug situations. Wouldn’t it be better if z-index could be compared globally?

I don’t think so. Here are some of my reasons:

  1. If we have a very complex structure, we need to assign z-index to many, many elements, z-index inflation to understand!

  2. While I’m not a browser engineer, I can imagine that the current design will help the browser perform more efficiently. Without the current design, the browser would have to compare many elements with z-index, which feels like a lot of extra work!

  3. When we understand the context, we can use it to seal elements. This is a very powerful pattern in component-driven frameworks such as React.

This last point is the most interesting, let’s see more of it!

Isolation is used to achieve the fully sealed abstraction

Now I’m going to tell you about one of my favorite and most complex CSS properties: Isolation, the hidden treasure boy in the language.

You can use it like this:

.wrapper {
  isolation: isolate;
}
Copy the code

When we use this to declare an element, it does one thing: create a new context.

Why are there so many ways to create a context? Haha, since all other methods implicitly create the context, other changes may change the result. But Isolation creates context in the purest and simplest of ways.

  • We don’t have to specify z-index
  • Can be used on static positioned elements!
  • Rendering of child elements is not affected

Again, because this is awesome! Isolation can be used on static located elements! It allows us to seal its children!

Let’s look at another example. Recently I built a great envelope kit.

When you move your mouse over the opening, a letter will appear like this

The structure of the envelope component is divided into four parts (from left to right: the back of the envelope, the back of the envelope, the letter, the front of the letter)

I’ve packaged this structure together with the React component and it looks like this (I’ve used the introverted style for simplicity).

Function Envelope({children}) {return (<div> <BackPane style={{zIndex: 1}} /> <div style={{zIndex: 1}} <Flap style={{zIndex: 4}} /> <Flap style={{zIndex: isOpen? < div style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; white-space: inherit! Important;"Copy the code

(You may be wondering why the opening post z-index is dynamic, because it needs to be displayed at the back of the letter when opened.)

A good React component is one that can be used anywhere and is a standalone enclosure, like a space suit. So far, our spacesuit unfortunately has a leak.

When I put this component next to a header with z-index: 3, our hierarchy and header are confused as shown in the figure above. Because our Envelope is wrapped around four layers by div, it doesn’t create a context itself.

However, we can add isolation: ISOLATE to the div on the Envelope component to ensure that the components are grouped together.

function Envelope({ children }) {
  return (
    <div style={{ isolation: 'isolate' }}>
      <BackPane style={{ zIndex: 1 }} />
      <Letter style={{ zIndex: 3 }}>
        {children}
      </Letter>
      <Shell style={{ zIndex: 4 }} />
      <Flap style={{ zIndex: isOpen ? 2 : 5 }} />
    </div>
  )
}
Copy the code

So, why don’t we use the previous method position: relative; Z-index: 1 to create the context? That’s because the React component is expected to be reused, and z-index: 1 isn’t guaranteed to be correct in other cases. The beauty of Isolation is that you can guarantee that the component will always be flexible.

Browser support for Isolation is not a new familiarity, it has good browser support. It can be used on any browser except Internet Explorer.

If you want to support it in Internet Explorer, you might consider using Transform: translate(0px).

The Debug context

Unfortunately, I didn’t find many tools to help debug context.

Microsoft Edge has an interesting “3D view “to help show the structure of the context.

In fact, this is something that I feel and look tired of, and it doesn’t feel like it can help me locate the elements in my app, or help me understand the context in my app.

In fact, I have another little idea, which is offsetParent.

const element = document.querySelector('.tooltip');
console.log(element.offsetParent); // <main>

Copy the code

OffsetParent returns the ancestor node of the nearest position with a non-static value (relative, Absolute, fixed).

Note: This is not a perfect solution. Not all contexts are laid out with position, and not all elements positioned in position create a context! But this method is at least a starting point for testing.

If you have new ideas, you can contact me on Twitter! : twitter.com/JoshWComeau

Update 1: Felix Becker tells me about a VSCode plug-in that can be used to highlight the code generated context:

(This plug-in can be used in.css and.scss files)

Update 2: Giuseppe Gurgone told me that a Google Chrome plugin can add a new “Z-index” to DevTools.

Update 3: Andrea Dragotta has invented an awesome browser plugin that lets us see a bunch of super useful information about z-index and context:

This thing is really awesome and I’ve been using this plugin a lot lately.

Google firefox