Sassy Z-Index Management For Complex Layouts

Advertisement

Z-index is an inherently tricky thing, and maintaining z-index order in a complex layout is notoriously difficult. With different stacking orders and contexts1, keeping track of them as their numbers increase can be hard — and once they start to spread across CSS files, forget about it! Because z-index can make or break a UI element’s visibility and usability, keeping your website’s UI in working order can be a delicate balance.

Because z-index is contextual2, once you start using it, it can be easy for other elements to start requiring it as well. Finding z-index: 99999 rules scattered throughout a website is not uncommon, but the infamous 99999 was simply born of frustration. It is often misused as an easy way to force an element above everything else, but z-indexes are not always so straightforward. It is also not entirely scalable: What if you need to add something on top of that?

Another common strategy is to increment z-index values3 by double digits, so that you have room to insert other elements later, but that doesn’t solve the problem of keeping track of them all, and it doesn’t scale well when you want to add ones in between.

Every z-index instance raises a number of questions:

  1. Why does this element have this z-index value? What does it mean in the context of every other element?
  2. Where does it fit in the order and context of other z-index values? If I increase this one, which others are affected?
  3. If I add an element to the stacking order, which z-index values do I have to increase accordingly?

Using Sass To Maintain Order

With Sass, controlling all of the factors above is easy using lists. Let’s use Behance4’s home page as an example:

With Sass, it is easy to control all of the factors above using lists.5
Controlling all of the factors above is easy using lists. (View large version6)

We need to maintain the stacking order of the project covers, filter bar, location search modal, custom drop-down above the modal, and website navigation, in that order, from bottom to top. We can set up a Sass list, like so:

$elements: project-covers, sorting-bar, modals, navigation;

This list represents the order in which we want our elements to appear, from lowest to highest, with each index, or position, in the array representing the z-index of that element. We can use the Sass index function7 to assign a z-index value to each element.

For example:

.project-cover {
   z-index: index($elements, project-covers);
}

This would print out:

.project-cover {
   z-index: 1;
}  

This is because project-cover is the first element in the list, at index 1, and the lowest element in our z-index stacking order. Let’s write the Sass for the rest of the items in our list:

.sorting-bar {
   z-index: index($elements, sorting-bar);
}
.modal {
   z-index: index($elements, modals);
}
.navigation {
   z-index: index($elements, navigation);
}  

Now, our compiled CSS would look like this:

.project-cover {
   z-index: 1;
}
.sorting-bar {
   z-index: 2;
}
.modal {
   z-index: 3;
}
.navigation {
   z-index: 4;
}   

Now you can apply the class to your elements accordingly. But what if we want to add an element to the existing stacking order? Suppose we wanted to add a tooltip that appears when the visitor hovers over a username, above the project covers but below everything else.

With Sass, we just have to update our list with the new element.8
With Sass, we just have to update our list with the new element. (View large version9)

With vanilla CSS, this change would mean having to update the z-index values of the sorting bar, modal and navigation. With Sass, we just have to update the list with our new element:

$elements: project-covers, user-tooltip, sorting-bar, modals, navigation;   

Because the z-index values of the sorting bar, modals and navigation have changed in the list (they had values of 2, 3 and 4 but now are 3, 4 and 5), our compiled CSS will automatically adjust their z-index values using their new positions. Let’s add this new line of Sass:

.user-tooltip {
   z-index: index($elements, user-tooltip);
}  

This will make the compiled CSS look like this:

.user-tooltip {
   z-index: 2;
}
.sorting-bar {
   z-index: 3;
}
.modal {
   z-index: 4;
}
.navigation {
   z-index: 5;
}

Scaling The Solution Across Stacking Contexts

Suppose our layout is even more complex, with multiple stacking contexts and stacking orders. (Remember that in order for an element’s z-index value to have an effect, its position must not be static. This is what creates a new stacking context, giving any children of the element a stacking order specific to its parent.) In this case, one linear list might not be sufficient. Instead, we can create as many lists as we need, each of which would be considered one context.

You can create as many lists as you need, where each list is considered one context.10
Create as many lists as you need, each of which would be considered one context. (View large version11)

$elements: project-covers, user-tooltip, sorting-bar, modals, navigation;
$modal-elements: fields, form-controls, errors, autocomplete-dropdown;

.modal {
   z-index: index($elements, modals);

   .field {
      z-index: index($modal-elements, fields);
   }
   .form-controls {
      z-index: index($modal-elements, form-controls);
   }
   .error {
      z-index: index($modal-elements, errors);
   }
   .autocomplete-dropdown {
      z-index: index($modal-elements, autocomplete-dropdown);
   }

} /* .modal */

This compiles to:

.modal {
   z-index: 4;
}
.modal .field {
   z-index: 1;
}
.modal .form-controls {
   z-index: 2;
}
.modal .error {
   z-index: 3;
}
.modal .autocomplete-dropdown {
   z-index: 4;
}   

Scaling The Solution Across A Website

The most important requirement of this technique is sticking to it. Any rogue hard-coded z-index values could compromise the integrity of those derived from your list. Because of this, you might find that you need to maintain the z-index order across several pages of a website. The simplest solution is to create a partial containing your site-wide lists, which you can then include anywhere you need it. (You might already have a partial that you include everywhere that stores variables for your website’s colors, font sizes, etc. — this is the same idea.)

_zindex.scss

$elements: project-covers, user-tooltip, sorting-bar, modals, navigation;
$modal-elements: fields, form-controls, errors, autocomplete-dropdown;   

mypage.scss

@import '_zindex.scss'

.modal {
   z-index: index($elements, modals);
}   

Combining or modifying global lists per individual page is also possible. You can import your partial and then use Sass’ list functions12 (or other advanced ones13) to modify as needed. For example:

@import '_zindex.scss'

$modal-elements: append($modal-elements, close-button);
$elements: insert-nth($elements, sidebar-filters, 3);

.modal .close-button {
   z-index: index($modal-elements, close-button);
}
.sidebar-filter {
   z-index: index($elements, sidebar-filter);
}   

This Sass would add a “Close” button to the end of the modal stacking order and insert birds into the main stacking order of the page it’s included on, all without affecting other pages that use the _zindex.scss partial.

Error-Reporting

Always check for errors in your code to avoid mistakes. For example, if you tried to get the z-index value of an item not in your list, you might find an unexpected output:

.objects {
   z-index: index($elements, thing-not-in-my-list);
}
.objects {
   z-index: false;
}

Because false isn’t a valid value for z-index, we don’t want it in our compiled code. We can stop this from happening by making a custom function that acts as a proxy to the call to list and that uses Sass’ @warn to tell us whether something has gone wrong.

@function z($list, $element) {

   $z-index: index($list, $element);

   @if $z-index {
      @return $z-index;
   }

   @warn 'There is no item "#{$element}" in this list; choose one of: #{$list}';
   @return null;
}   

This function takes the same arguments as index, but before it returns the value, it checks that we are requesting something that exists in the list. If the value exists, then it returns the result of the index call, just like before. If the value does not exist, then two things happen:

  1. A warning is printed telling you that the item you’re requesting is not in the list, and the values of the list are printed so that you can see what to choose from.
  2. The value returned is null, which tells Sass not to print out the rule at all.

So, instead of the following invalid CSS…

.objects {
   z-index: false;
}   

… the z-index would not get printed at all. As a bonus, if it was the only rule in your rule set, then Sass would not even print the selector, thereby minimizing unnecessary output.

Conclusion

Keeping track of stacking contexts and orders in CSS is hard, but variables in preprocessors make it much easier. There are many ways14 to handle it, but the approach we’ve looked at here is intended to be the most straightforward and require the least amount of maintenance, by using simple lists of items in the order they should appear. And because Sass lists and functions are so powerful, you have endless possibilities for expanding this technique to be as comprehensive as you need!

Resources

(al, ml, il)

Footnotes

  1. 1 http://philipwalton.com/articles/what-no-one-told-you-about-z-index/
  2. 2 http://www.smashingmagazine.com/2009/09/15/the-z-index-css-property-a-comprehensive-look/
  3. 3 https://github.com/twbs/bootstrap/blob/master/less/variables.less#L250
  4. 4 http://behance.net
  5. 5 http://www.smashingmagazine.com/wp-content/uploads/2014/06/01-behance-opt.jpg
  6. 6 http://www.smashingmagazine.com/wp-content/uploads/2014/06/01-behance-opt.jpg
  7. 7 http://sass-lang.com/documentation/Sass/Script/Functions.html#index-instance_method
  8. 8 http://www.smashingmagazine.com/wp-content/uploads/2014/06/02-behance-opt.jpg
  9. 9 http://www.smashingmagazine.com/wp-content/uploads/2014/06/02-behance-opt.jpg
  10. 10 http://www.smashingmagazine.com/wp-content/uploads/2014/06/03-behance-opt.jpg
  11. 11 http://www.smashingmagazine.com/wp-content/uploads/2014/06/03-behance-opt.jpg
  12. 12 http://sass-lang.com/documentation/Sass/Script/Functions.html#list-functions
  13. 13 http://hugogiraudel.com/2013/08/08/advanced-sass-list-functions/
  14. 14 http://css-tricks.com/handling-z-index/
  15. 15 http://www.smashingmagazine.com/2009/09/15/the-z-index-css-property-a-comprehensive-look/
  16. 16 http://philipwalton.com/articles/what-no-one-told-you-about-z-index/
  17. 17 http://css-tricks.com/handling-z-index/
  18. 18 https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
  19. 19 http://sass-lang.com/documentation/Sass/Script/Functions.html#list-functions
  20. 20 http://hugogiraudel.com/2013/08/08/advanced-sass-list-functions/

↑ Back to topShare on Twitter

Jackie Balzer is the head of front-end development at Behance, the world's leading online platform for showcasing and discovering creative work. She leads a team of front-end developers focusing on modular, scalable and reusable CSS with Sass and Compass, while bringing rich, complex designs to life with harmonious CSS and JavaScript. When not coding, she loves traveling, eating, and drinking outdoors.

Advertising
  1. 1

    Ahhh, the end of World War Z-index! Awesomeness.

    9
  2. 2

    Great article! Going to implement this solution in a project later today, thanks :)

    2
  3. 3

    This is awesome. I hate seeing 10000 when all that is needed is 3. Just doesn’t seem right.

    3
  4. 4

    And for those of us who don’t use a CSS Pre-processor, we should just continue to make sure we’re using sensible z-index values. I hate seeing two or three instances of z-index with values like 9999 or 100 or 50000.

    4
    • 5

      Use a CSS-Preprocessor. Seriously.

      8
      • 6

        Or don’t use a CSS preprocessor. Seriously.

        If I can quickly write tightly-organized, plain CSS, where is the incentive to switch?

        2
        • 7

          Did you ever tried one?

          -1
          • 8

            I have, but I still don’t see the value. Most of the benefits are easily disproved by taking a quick look at the CSS output of sites that were styled with SASS/LESS/etc. I would argue that most proponents of using a preprocessor just don’t have a good handle on CSS. Seriously, if anyone out there has an example of well-written CSS from a preprocessor, I’d like to see it.

            -1
        • 9

          Anne – that’s all well and good for a small site, but try scaling out a site with thousands of pages by hand-writing your CSS. Preprocessors are super useful because they allow you to architect your CSS in a way where you can provide each page with a different set of CSS that’s only applicable to said page. You can create a lot of dynamic rulesets using mixins, and preprocessors also help with little things like not having to repeat every nested set of rules and not writing out arcane gradient syntax every time for every browser. Remember, CSS isn’t written for humans to read, it’s for the machine to interpret, so it doesn’t matter how pretty it is (you should be minifying anyways).

          Believe me, I know where you’re coming from, I used to be the same way. My former boss forced me to try a preprocessor for a project and after 20 minutes I couldn’t believe how easy it was and how much time it saved. I recommend giving it a shot ;) If you want some help getting started, I wrote an article on how to architect your SASS properly: http://blog.mightyspring.com/post/58803131171/purposefully-architecting-your-sass

          6
          • 10

            Ok, I can see where this would make it much easier to manage the styles for a huge site. Thank you for spelling it out for me; I see so many one-sided, “OMGSASSISAWESOMETRYIT!!1!” arguments that it’s hard to know what to believe. Thanks for the link – I will check it out!

            3
      • 11

        Honestly, when you provide no legitimate reason why, it’s harder to understand the sentiment.

        Having used one on a few projects, I still find myself making necessary changes and tweaks in CSS directly, and not even bothering with SASS for smaller projects. Anne has a very valid point regarding the appearance of many sites out there that you can tell were hacked together by someone who seemed limited in their CSS abilities- or made sure the basic contents were established, but didn’t bother getting down to detail — that could either be the designer’s issue, or and issue with having to create 30 mixins just to do something you could have written out (and copy/pasted or duplicated- depending on your IDE) in half the time.

        To each their own, and there are benefits to knowing how to actually code, versus relying on a tool to help, but to outright tell people what they should do only makes you seem pompous, and doesn’t help your argument at all.

        0
  5. 12

    Great stuff!

    0
  6. 13

    Anders Grimsrud

    June 12, 2014 5:04 pm

    Now I’m just annoyed I haven’t thought of this before. Thanks for a great idea and a well written article!

    0
  7. 14

    This is the best use of SASS I’ve seen. Other uses for it seem unimportant, since I keep my CSS well organized and Find & Replace does all the work I need.
    Your article actually seems very useful.

    0
  8. 15

    Great article! Very useful.
    Thanks.

    0
  9. 16

    This is a very good article – _z-index.scss here I come :)

    0
  10. 17

    Great Article ! Thanks a lot

    0
  11. 18

    I’m not sure if it’s the sites I tend to build or what. But I tend to just use the Van Halen method of numbering and using z-index.

    812
    1984
    5150

    and I rarely (I can’t even think of the last time I did) need any more than this.

    5
  12. 19

    The Idea is good, but with pure lists and the resulting index number, you will many times run into problems (sometime you want to shift the z-index to a higher Number).

    With a data-map you get more control:
    http://sassmeister.com/gist/29202828a1c37714f5e1

    1
  13. 20

    I like using Sass when it comes to z-index, because it’s always a bit complicated to deal with.

    Your solution looks clean, meanwhile I’m not sure I like using list indexes as z-index. It has occurred to me having a large gap between 2 indexes allows me to add another index between them.

    With your solution, adding an intermediate index would require a `insert-nth` function, which doesn’t ship with Sass. So you need to write your own function, which is not ideal for something like this.

    This is the reason I’d rather use a map, with generic names. Here is a proof of concept: http://sassmeister.com/gist/11172138.

    Cool article nevertheless. :)

    3
  14. 22

    Whoa really usefull!!! thanks :)

    0
  15. 23

    Interesting idea! I wanted to point out that you can use @each and #{} to reduce your code and let Sass do more for you. This way if you need to add/remove/rearrange any item in your z-index list you don’t have to update any other code other than the list itself.

    Here is a modified example:
    http://sassmeister.com/gist/9868ba071b38856bd27a

    1
  16. 24

    Thanks for this Jackie. I’ve added this one to the fold for my SASS globals. I avoided SASS for so long but I really don’t know how I was getting by without it.

    0
  17. 25

    Anyone knows a mixing to use with LESS?

    0
    • 26

      Since LESS has only two list functions (length and extract) and nothing similar like Sass’s index-function, I don’t think you can get a mixin as comfortable as Jackies approach in Sass.

      0
  18. 27

    “(…) the infamous 99999 was born of frustration. It’s an easy way to get an element just to be on top of everything else.”

    Unfortunately, the author of this article does not seem to fully grasp z-index herself; setting an element’s z-index to 99999 most definitely does not guarantee any such thing, and the answer to why lies earlier in the same paragraph: it is contextual!

    0
    • 28

      I updated the copy in that paragraph to be more clear, since I see it could have been misleading: I wasn’t advocating 99999 as an end-all-be-all solution, I was just using it as an example of a bad practice that my technique aims to help solve :) Hopefully you find the rest of the contents of the article informative and useful. Try it out and let me know what you think!

      1
  19. 29

    I think I will probably go with Chris Coyier’s approach (a _zindex partial containing a map of element names to their z-index values) simply because this would play more nicely with third party plugins. You can’t know what z-index values a plugin might use and I don’t want to have to edit the plugin source code so an approach where you explicitly specific the z-indexes would be more flexibly – although obviously you could run into some problems if you don’t specify wide enough gaps and that solution is possibly not quite so neat.

    0
  20. 30

    This is awesome! I worked on a similar problem myself, but didn’t come up with anything quite so sophisticated: http://viget.com/extend/sass-maps-and-z-indexes

    0
  21. 31

    David Fox-Powell

    June 17, 2014 3:44 pm

    Hi, this is an awesome post, I always cringe a little when I use the 99999 hack. It inspired me to write a couple basic mixins for keeping track of z-index as you suggested. There are @for and @each loop examples, but I don’t think the @each would work well for the nested example.

    https://gist.github.com/dtothefp/128e8f44eeee4e34b1ff

    0
  22. 32
  23. 33

    Jon R. Humphrey

    June 18, 2014 10:02 am

    Jackie, et al,
    Thanks for another keen insight into managing z-index with sass, I commented on Doug Avery’s method the other day and have to agree with his conclusion on yours! :-D

    I find Hugo’s idea of using Sass’ math abilities to manage the changes even more appealing however I still wish there was a way to reference a parent element’s value which could make things even more robust … but I digress! ;-)

    These snippets make our work-loads just that much lighter so thank you once more!

    Jon

    PS. in the “MYPAGE.SCSS” second code example, I think there’s an extra “s” on the $elements list item: sidebar-filters as it’s not the same as the referenced class or the z-index method parameter?

    0
  24. 35

    Like your approach for a project I did earlier I did z-indexes using Sass mapes like:

    http://sassmeister.com/gist/0e50121b6c84eef0d6c9

    0
  25. 36

    Really useful!! Thanks

    0
  26. 37

    Felicity Evans

    July 3, 2014 2:58 am

    Unfortunately z-index gets far more complex when dealing with ads. We often end up with distressingly large values for drop-down menus due to IAB advertising guidelines which specify expanding advertising should fall between the range: 5,000 – 1,999,999 !!

    0
  27. 38

    Hi!
    Thank you very much for the article – this is amazing!
    One day I’ve faced the same need but in LESS. As it doesn’t have built in functions to get index of item from the list, I’ve made some trick to do so. So, here is the code for those who are interested in LESS version :)

    @containerElements: “container1″, “container2″, “container3″, “container4″, “container5″, “container6″, “container7″;

    .zindex(@elementName) {
    .loop(@elementName, length(@containerElements));
    }

    .loop(@elementName, @counter) when (@counter > 0) {
    .loop(@elementName, @counter – 1);
    .pickIndex(@elementName, @counter);
    }

    .pickIndex(@elementName, @counter) when (extract(@containerElements, @counter) = @elementName) {
    z-index: @counter;
    }

    this could be extended even more if we move @containerElements to the parameter of .zindex() mixin

    0

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top