Grow your CSS skills. Land your dream job.

Handling z-index

Published by Chris Coyier

Managing z-index across large sites can be a pain in the butt. CSS is hard to test, so it's notoriously easy to make a change that ends up, for instance, hiding some important UI under who-knows-where.

If you use a CSS preprocessor, maybe we can handle it in a special way.

An Idea From Game Programming

Vertical stacking order is not only a problem for web design, it's a major issue for game developers too. They work in even more dynamic environments where it is vitally important which things appear on top of others.

That "A" better appear on top of the modal, which needs to appear above any characters, which need to appear above the background layer...

In a brief conversation with Ryan Campbell, who is digging into the world of games, he says the way it's normally handled is a enum (kind of like a named array of constants) variable which then you reference as needed.

Here's an example of some code he might use:

typedef enum : uint16_t {
  WorldLayerGround             = 100,
  WorldLayerBelowCharacter     = 200,
  WorldLayerAboveCharacter     = 400,
  WorldLayerTop                = 800,
  WorldLayerCount              = 1600
} WorldLayer;

That is defined in a file dedicated to declaring constants for the game to use. Notice how:

  1. They are all declared together
  2. There is space left between the numbers

It's fairly common to see people number in the hundreds with z-index in web design too. The idea being that you could slip something in between later if need be, which you couldn't if you did 1, 2, 3, etc, because z-index doesn't support decimals. But rarely do you see z-index declared in a group like that. Seems like a smart idea to me!

Grouping

The idea is that all the code that controls vertical stacking is in one place. You could just put all that stuff together.

.modal {
  z-index: 9000;
}
.overlay {
  z-index: 8000;
}
.header {
  z-index: 7000;
}

But I'm not a huge fan of that. I don't like duplicating selectors throughout stylesheets just for organization. Just a personal preference. When I find where a class is defined, everything that that class does is there.

Perhaps we can do the same thing but just with the values...

A stock photography metaphor!

A z-index Partial

Perhaps the single most useful feature of preprocessors is the ability to do includes. Smush multiple files together into one output file. That means you can organize your CSS however makes sense to you, which almost surely means breaking down into small, modular bits.

Maybe you already have a variables.styl or constants.less or something. You could get even more modular and have a file just for managing z-index. In SCSS, the typical naming convention for files only meant to be included start with an underscore, so we'd make a _zindex.scss.

In that file, you'd declare variables that contain all the z-index values you use throughout the site.

$zindexModal   : 9000;
$zindexOverlay : 8000;
$zindexHeader  : 7000;

Then in your global set of includes for other SCSS files, you always have this available:

@import "zindex";

And use them as needed:

.header {
   z-index: $zindexHeader;
}

The idea would be go absolutely whole-hog on this. Do not declare z-index anywhere without making a variable for it and placing it within the stack in the _zindex.scss file.

Maps might be cleaner

Same idea, but using a Sass map instead of individual global variables:

$zindex: (
  modal     : 9000, 
  overlay   : 8000,
  dropdown  : 7000,
  header    : 6000,
  footer    : 5000
);

.header {
  z-index: map-get($zindex, header);
}

I think I'll be converting sites over to this system. I don't see any downside and plenty of upsides.

Comments

  1. Permalink to comment#

    Wow, I really dig this idea, especially for large projects that multiple developers are working on. Whenever I come across a z-index issue and need to change a value I’m worried that the value I changed was set in conjunction with another element elsewhere in the site/app that I’m not aware of, and often times changing a z-index value to solve an issue just creates another issue somewhere else. It’s really easy to loose track of what values are doing what, and this would help out a lot with that. I suppose if not using this technique the very least I could do is include a comment explaining the value and what other elements it correlates to.
    Thanks for the idea though, brother!

  2. Mike Gibson
    Permalink to comment#

    About 6 months ago I swapped to using sass variables for z-index and haven’t regretted the choice one bit. At first, we got some questions from our devs as to why we were creating variables for single purpose items, but when I explained the benefits of avoiding stacking conflicts, the lightbulb went off. This simply makes working with z-index pain and trouble free.

  3. Adam
    Permalink to comment#

    There is a typo, “Maybe ou already have a variables.styl” should be “Maybe you already have a variables.styl”

  4. I like this idea a lot. I know Bootstrap uses this approach as well. We’ve also adopted it into our own code base at work.

    If you have a documented coding style, adding a note about this technique helps other developers so you don’t find random instances of z-index not using this approach peppered throughout your code.

  5. Awesome! I love that someone with as much influence as you has finally laid out a proposed z-index standard. Love the SASS take on it too.

  6. I’ve been using something similar for a while that I call the “Z-Index Index”: https://github.com/danielmall/danielmallcom/blob/master/-/c/_scss/base.scss (starting on line 27)

  7. I’ve always just used a stacking partial for this utility too but instead I use data-order="100" data attribute instead of a class. For me it’s a cleaner separation but it’s just a personal thing.

    • Paul d'Aoust

      Sounds interesting, and I can appreciate how that sort of thing could be considered the responsibility of the content layer. Can you tell us more about that?

  8. William
    Permalink to comment#

    Hate to be the one that notices this and points it out … “nortiously” ?

  9. fooman
    Permalink to comment#

    Sass maps…. didn’t even know they existed!
    Is there a blog somewehere that has a posting like “Sass Features You Didn’t Know Existed”? haha (for real tho)

    • Bridget
      Permalink to comment#

      I didn’t realize Sass Maps were a thing, either. Now I’m off to learn all I can about them to determine how to clean up the Sass I currently have. Nice!

    • Adrián
      Permalink to comment#

      Please, RTFM. :)

    • Greg Lilley

      Aside from the official sass docs, Hugo has some good stuff on improving your sass workflow with more advanced features: HugoGIRAUDEL.com.

      I was going to say I’m surprised he hasn’t chimed in yet, but of course he has. Just farther down.

  10. Thad Bloom
    Permalink to comment#

    Useful information, I just wanted to comment and say how awesome Final Fantasy 3 is.

  11. Ken Brazier
    Permalink to comment#

    The problem I have with z-index is that it only works for elements that are position:absolute, position:relative, or position:fixed.

    How do you handle this for sites where you use z-index a lot? Just set everything to * {position:relative} ? If so, how do you handle absolutely positioning an element in an element several levels above it? Or do you just avoid that as bad practice?

    Or does somebody have a Sass function that combines the above techniques with setting every element with z-index to position:relative by default?

    • You can’t change the existing system. z-index must always be used on positioned elements. I don’t see how that’s a problem. There are elements that still needs to be static and will not need positioning of any kind

  12. Charlie Cathcart
    Permalink to comment#

    This is how I’ve been handling z-index for the current web app I’m working on. With only 2 front-end devs, and 2 dozen+ back-end, setting up a Sass file with variables for each item that needed a z-index, and then documenting that in our pattern library has reduced issues with layering to 0.

  13. @Fooman — this will give you an intro to Sass maps: http://benfrain.com/using-lists-with-maps-in-sass-3-3/

    • Bridget
      Permalink to comment#

      Thank you! Off to read.

    • I also never used maps but from the use in the article, I think I can take off with SASS maps already even without an article. Thanks for the link anyway. I’ll try to check it out

  14. Chris Z
    Permalink to comment#

    In the future, I could see this being done with CSS variables. Outside of current support limitations, I doubt that there is any better way of doing this.

    :root {
        var-z-modal     : 9000; 
        var-z-overlay   : 8000;
        var-z-dropdown  : 7000;
        var-z-header    : 6000;
        var-z-footer    : 5000;
    }
     .modal {
         z-index: var(z-modal);
     }
    
  15. Jcyzc
    Permalink to comment#

    Ok, it was nice to try and use C++11′s typed enums, but uint8_t limits the values to the [0, 255] range so…

    Better luck next time?

  16. Brilliant solution. Z-index is definitely one of those things that just lends itself to becoming messy and confusing. Anything that attempts to alleviate that problem is welcome Going to be switching to this approach I think.

  17. Ciaran
    Permalink to comment#

    Nice solution, but those z-indexes are way high. Any reason for them starting at 5000? I do prefer my z-indexes to stay under 100, personally. I see so many sites with z-indexes ranging from 0-999999, which is just a bit ridiculous.

  18. Brady
    Permalink to comment#

    This is great, I was not aware or mapped values in SCSS!

  19. Permalink to comment#

    Great one there Chris! I begun making color palettes with Sass maps and it was a great experience. Now Z-index goes into the list as well.

    Personally I would build a additional function to grab the z-index values instead of typing map-get too many times. Here’s an example of it would look like

    $zindex: (
      modal     : 9000, 
      overlay   : 8000,
      dropdown  : 7000,
      header    : 6000,
      footer    : 5000
    );
    
    
    @function zindex($level: 'modal') {
        @return map-get($zindex, $level);
    }
    
    .modal {
      z-index: zindex(modal); 
    }
    

    It’s just going one more step, but I like this approach. Would be great if you guys found this useful

  20. Nicole
    Permalink to comment#

    Awesome!

  21. I use a similar approach. I don’t like to have these arbitrary high z-index numbers, and then adding new ones in between. It just gets messy after a while.

    Instead, I just use a list, and get its index from there:

    $stacking: header,
               sidebar,
               menu,
               popup;
    
    
    @function z($el) {
        @return index($stacking, $el);
    }
    
    .header {
        z-index: z(header);
    }
    

    This way I don’t have to fiddle around with any numbers, and adding a new item in the stack becomes really trivial.

  22. Well, this sounds really nice but – as mentinoned before – this all comes down to nothing because of the concept of stack contexts in CSS. A pity, but I fear z-index remains a pain in the a**.. (which is in the nature of its design and cannot be overcome with assiting tools and technologies like Sass). I wish I’d be wrong.. :(

  23. i’m working on a huge web app and i’m the only one on the team who’s in the CSS. This idea is huge help. Thanks. When’s your book available to order? hint hint

  24. Luke
    Permalink to comment#

    This sounds great for completely self-written code, but what about when using libraries that don’t want you setting z-index (like jQuery-dialogs) ?

  25. Adam
    Permalink to comment#

    It’s extremely rare that I have to resort to z-index for anything on nearly every site I work on. What are you guys using it for exactly?

    • Same here, and in the few cases where I do need to use z-indexes, properly setting the stacking context deals with it for me.

  26. joan

    My practice is that the z-index of every positioned or transformed element to be placed above something appearing later in the document flow should be just 1 or 2, and if the z-index were local, make the local scope – which has a position: relative or transform or the like, set its z-index: 0. Also, the container of the whole page except for elements with global z-indexes (well, indices) has a position: relative; z-index: 0; and for its globally z-indexed siblings, just assigning 1 or 2 is fine.

  27. joan

    [M]ake the local scope – which has a position: relative or transform or the like, set its z-index: 0.

    Sorry for a typo.
    Set the z-index of the local scope – which should have a position: relative or transform or the like – 0.

  28. Here’s a fairly elegant solution I’ve come up with to handle complex z-indexing, basically abstracting the idea of z-index to just ‘layers’ and letting Sass do the tedious work. This gives it a more ‘designery’ approach and allows you to just list out your layers visually without explicitly declaring any z-index values. In a _config.scss:

    $layers:
        ".modal", // top
        "#overlay", 
        "#controls-toolbar",
        "#progress-bar",
        "#LiveclickerVideoEmbed"; // bottom
    
    @include layers($layers, 10, 0); // defaults shown
    

    In a _mixins.scss:

    // Manage Z-indexes
    @mixin layers($layers, $between: 10, $bottom: 0) {
         //$between: 10; [default] gives us 10 'slots' between layers to stack things
         //$bottom: 0; [default] bottom layer value
        $i: length($layers);
        @each $layer in $layers {
            #{$layer} {
                z-index: $between * $i + $bottom; 
            }
            $i: $i - 1;
        }
    }
    

    This can be used ‘between’ layers, too, should you need to ‘shim’ another set of z-indexes into place:

    $controls-layers:
        ".play-button", // top
        ".volume-controls"; // bottom
    
    @include layers($controls-layers, 1, 11);
    

    Result would spit out appropriately z-indexed ‘layers’ between the 1st and 2nd layers (from $layers).

    I haven’t spent much time optimizing this or extending it as it meets my needs for now, but I’m sure there’s more functionality you could add on top of this if so desired. Maybe a function to return the z-index of a layer, but I haven’t needed to go there yet.

    Let me know your thoughts, Chris, I’m a long-time reader first-time poster ;)

    -Mike McCormick

  29. Here’s the output, should have included that:

    .modal { z-index: 50 }
    #overlay { z-index: 40 }
    #controls-toolbar { z-index: 30 }
    #progress-bar { z-index: 20 }
    #LiveclickerVideoEmbed { z-index: 10 }
    .play-button { z-index: 13 }
    .volume-controls { z-index: 12 }
    

    And you can see I should have used this:

        @include layers($controls-layers, 1, 10);
    

    Rather than this:

        @include layers($controls-layers, 1, 11);
    
This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".