Layouts in CSS: Positioning and Stacking Elements

Every element in a document participates in a stacking context. The stacking context is a model or set of rules for how elements are painted to the screen. If you’ve ever used the z- index property, you’ve worked with stacking contexts.

The root <htmt> element creates a root stacking context. Some CSS properties and values can also trigger a stacking context for the elements they’re applied to. Whether part of a root or local context, children within a stacking context are painted to the screen from back to front as follows:

  • child stacking contexts with a negative stack level (for example, positioned and with z- index: -1 )
  • non-positioned elements whose computed position value is static
  • child stacking contexts with a stack level of 0 (for example, positioned and with z-index: auto )
  • child stacking contexts with positive stack levels (for example, positioned and with z- index: 1 )

If two elements have the same stack level, they’ll be layered according to their order in the source HTML.

Let’s look at an example. Here’s our HTML:

<div id=”a”>

<p><b>div#a</b></p>

</div>

<div id=”b”>

<p><b>div#b</b></p>

</div>

<div id=”c”>

<p><b>div#c</b></p>

</div>

<div id=”d”>

<p><b>div#d</b></p>

</div>

<div id=”e”>

<p><b>div#e</b></p>

</div>

And here’s our CSS:

#a {

background: rgba( 233, 30, 99, 0.5 );

}

#b, #c, #d, #e {

position: absolute;

}

#b {

background: rgba( 103, 58, 183, 0.8 );

bottom: 120px;

width: 410px;

z-index: 2;

}

#c {

background: rgba( 255, 235, 59, 0.8 );

top: 190px;

z-index: 1;

}

#d {

background: #03a9f4;

height: 500px;

top: 10px;

z-index: -1;

}

#e {

background: rgba( 255, 87, 34, 0.7 );

top: 110px;

z-index: 1;

}

This produces the stacking order shown in the image below.

The bottommost layer is #d , because its z-index value is -1. Since #a isn’t positioned, it sits above #d , but below the positioned elements ( #b , #c , and #e ). The next layer is #c , followed by #e . Since both elements have the same z-index value, #e is stacked higher, because it’s last in the source order. The topmost layer is #b , due to its z-index of 2 .

To make it a bit easier to visualize, the following image shows a three-dimensional projection of the above stacking context.

All of the elements in the previous example are part of the root stacking context. But let’s see how stacking is affected by the opacity property, which forces a local context when its value is less than 1 . Consider the following HTML:

<div id=”f”>

<p><b>div#f</b></p>

</div>

<div id=”g”>

<p><b>div#g</b></p>

</div>

It’s paired with this CSS:

#f, #g {

position: absolute;

}

#f {

background: rgba( 255, 193, 7, .9 );

}

#f p {

background: rgb( 34, 34, 34 );

color: whitesmoke;

position: relative;

z-index: 1;

}

#g {

background: rgba( 3, 169, 244, .7 );

top: 50px;

left: 100px;

}

According to the rules of the stacking context, #f p occupies the topmost layer in the stack. That’s what we see in the following image.

But if we change our CSS and add opacity: .99 to the #f rule set, something interesting happens:

#f {

background: rgba( 255, 193,7, .9 );

opacity: .99;

}

The opacity property creates a new stacking context any time its value is less than 1 . As a result, the z-index for its child element becomes relative to its parent rather than the root stacking context. You can see how this works below. Notice that #g now occupies the topmost layer.

Let’s add an absolutely positioned <div> element to #f and give it a z-index value of 2 . Now <div> is stacked on top of #f p (see below), but it’s still layered behind #g because #f has a local stacking context. Children of a local stacking context can only be reordered relative to that context. Elements that sit in other contexts can’t be layered within a local one.

Let’s look at an example of using the stacking context to manage layers and positioned elements. In this case, we’ll create a menu that slides in from the top of the screen. But rather than slide in over the logo and menu button, we’ll make it slide in beneath it. First, our HTML:

<header>

<img src=”dont-awesomenews.svg”>

<button type=”button” id=”menu”>

<img src=”dont-menu.svg”>

</button>

<nav>

<ul id=”menu-list”>

<li><a href=”/sports”>Sports</a></li>

<li><a href=”/politics”>Politics</a></li>

<li><a href=”/arts”>Arts &amp; Entertainment</a></li>

<li><a href=”/business”>Business</a></li>

<li><a href=”/travel”>Travel</a></li>

</ul>

</nav>

</header>

Clicking the <button> element causes the element to slide into view. Here’s our our (simplified) CSS:

header {

background: hsl( 206, 9%, 15% );

color: whitesmoke;

width: 100%;

}

nav {

background: hsla( 206, 9%, 15%, .9 );

position: absolute;

width: 100%;

left: 0;

top: -33vw;

transition: top 500ms;

}

.open {

top: 6rem;

}

The CSS above creates a menu that slides down from the top when triggered. But as it slides in, it passes over the AwesomeNews logo, as pictured below.

Our menu (the <nav> element) slides over the logo and menu button because it has a higher stack level. Remember that when multiple elements have the same z-index value, the last one in the source will be the topmost layer.

Let’s change this. What happens when we add z-index: -1 to the nav rule set? Well, you get the mess you see pictured below.

The navigation slides in behind the logo and menu button, but it also slides in behind the content. It’s hard to read and impossible to click.

Because its parent element ( <header> ) isn’t positioned and has a computed z-index value of auto , the <nav> element is still part of the root stacking context. Adding z-index: -1 shoves it to the bottom of the root element’s stack, which means it sits behind other elements in the root stacking context.

So how do we fix this? By creating a new stacking context on our <header> element, which is the parent element of <nav> . We already know that the opacity property can create a new stacking context when its value is less than 1 . However, positioned elements can also create a new stacking context.

Let’s add position: fixed to our <header> element. Now our <nav> element participates in the stacking context of header instead of the document root:

header {

background: #222629;

color: whitesmoke;

width: 100%;

top: 0;

position: fixed;

}

Since <nav> now participates in the stacking context of <header> , the menu sits above the rest of our content. But because <nav> has a negative stack level, it sits at the bottom of the <header> element’s stacking context, as illustrated below.

For the rest of this chapter, we’ll switch gears and talk about some newer modules for creating more complex layouts. We’ll learn how to wrap text around complex shapes, flow content across multiple columns, create more flexible components, and build complex grid layouts—all of which was previously difficult if not impossible, and often required extra markup or JavaScript.

Source: Brown Tiffany B (2021), CSS , SitePoint; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *