Managing Z-Index in Micro Frontends

Samuel Gómez
Valtech Switzerland
8 min readMay 5, 2021

In this article we analyze why z-index conflicts are specially hard to solve in in micro frontends as of 2021, compare the most recent approaches to solve them, and recommend a specific combination of them.

Challenge

Micro frontends are a modern approach to put web frontend products together between multiple teams.

Inspired by the idea of micro services, the micro frontend approach consists in one container application hiding and displaying a number of smaller, narrowly-tasked frontend applications.

This allows to reduce the time to market of the product, since loosely coupled parts of the UX can then be assigned to different teams that specialize in the business knowledge behind it.

However, there is a technical aspect hard to decouple: the position of overlaying boxes such as flyovers and modals along the z-axis.

As a result, it is not rare that two micro frontends that overlay their elements well enough when on their own, fail to do so when in the container application.

In this example, the overlay element is rendered in the right order.

Credit: Pavel Pomerantsev https://codepen.io/smashingmag/pen/VNwJoX/

In this other example, however, the overlay element is rendered in the wrong order.

Credit: Pavel Pomerantsev https://codepen.io/smashingmag/pen/jROgOE/

Source of trouble #1: It’s not just the z-index that matters

Here is the z-index deal, simplified:

  1. Just like train passengers are spread in passenger cars positioned along the rail, elements in a document are spread in stacking contexts positioned along the z-axis.
  2. So, as you already know: if you are at the back of the first passenger car, you are actually ahead of those at the front of the second passenger car.
  3. Similarly in CSS: if an element has a lower z-index but is in the stacking context closest to the user, it is actually painted on top of those elements with a higher z-index in a stacking context farther from the user.

Stacking contexts are not directly defined as a series but as a tree, because what creates stacking contexts are elements in the document tree. However, they are serialized during the painting order computation — so the train analogy holds.

Now let’s look at the W3C specification to learn the proper wording and remaining details. Sentences beginning with a number in parentheses are graphically depicted below.

In the CSS box model section we have

(1) The CSS box model describes the rectangular boxes that are generated for elements in the document tree and laid out according to the visual formatting model.

In the CSS positioning schemes section we have

(2) An element is said to be positioned if its position property has a value other than static

In the CSS layered presentation section we have

(3) Each box belongs to one stacking context.

(4) Each positioned box in a given stacking context has an integer stack level, which is its position on the z-axis relative other stack levels within the same stacking context.

Boxes with greater stack levels are always formatted in front of boxes with lower stack levels.

Boxes may have negative stack levels.

Boxes with the same stack level in a stacking context are stacked back-to-front according to document tree order.

(5) The ‘z-index’ property applies to positioned elements and specifies the stack level of the box in the current stacking context.

The root element forms the root stacking context.

(6) Other stacking contexts are generated by any positioned element having a computed value of z-index other than auto.

Stacking contexts are not necessarily related to containing blocks.

In future levels of CSS, other properties may introduce stacking contexts, for example ‘opacity’

Graphically:

About the last two sentences:

  • “stacking contexts are not necessarily related to containing blocks” implies: if a box does not create its own stacking context, it belongs to the same stacking context as its parent.
  • “future” actually means “present” as of 2021. There are now 15 other properties that introduce stacking context. You can find the complete list in MDN’s The stacking context.

Let’s see an example in which stacking contexts don’t map 1:1 to the document tree. In this example, left margins have been added to indicate the position of elements in the document tree, and paddings and right margins have been added to make overlapping more obvious.

This is how the example is rendered on screen:

This is how the document tree looks in 3D (root element on the left):

Note how the purple elements are most elevated (most to the right). That’s just because they are deepest in the document tree.

And this is how the resulting stacking contexts look in 3D (root stacking context on the left):

Now, the turquoise elements are the ones most elevated instead. That’s because they are in a stacking context higher than the purple ones.

How did this happen? Because these three conditions were met at once:

  1. The elements with stacking level 31 (purple) and stacking level 45 (turquoise) have the same parent stacking context (the root stacking context in this case).
  2. Both of them create a new stacking context.
  3. 31 is less than 45.

That’s it.

A small note: The element “position static” is not displayed in this third screenshot but should be. Seems like a bug of the tool I used (referenced at the end of this article).

Anyway, please note in the first screenshot that the element with stacking level -3 stays below the element with position static, which has stacking level zero because it is the default value.

Here’s the CodePen with the example: Document tree vs stacking context

Source of trouble #2: Multiple teams

The original article on micro frontends offers solutions and mitigations for a long list of the resulting risks, including some for styling.

However, managing z-index when two micro frontends are displayed simultaneously is not among them.

So essentially we have multiple teams working on different projects that form a single frontend experience, and their developers are creating overlapping boxes and don’t talk that much to each other.

Finding the right person, codebase and time to talk becomes more and more difficult with the complexity of your organization, architecture and aggregated set of frontend requirements.

And even when you find them and figure a potential solution together, since it might require changes in several of those projects, budget and schedule constraints to ship that solution will come not from a single team but from several.

A solution is required where the computed position of boxes in the z-axis can be reliably customized while minimizing the integration cost, including communication between teams.

Existing approaches

We found four existing approaches with at least one distinct fundamental idea.

Balzer, 2014: Sassy Z-Index Management For Complex Layouts

  • One list of z-index constants per stacking context
  • Ease the maintenance of those lists with Sass array operations

Pomerantsev, 2019: Managing Z-Index In A Component-Based Web Application

  • Each component should create its own stacking context and maintain its internal z-index values
  • When a component needs to create a flyover, appends it into <body>
  • Set z-index on all siblings or none for discoverability

Nguyen, 2019: Managing dynamic z-index in component-based UI architecture

  • Each component should create its own stacking context and maintain its internal z-index values
  • Divide the UI in partitions whose z-index can be promoted programmatically

Frieson, 2021: Managing CSS Z-Index In Large Projects

  • One list of z-index constants per stacking context
  • Ease the maintenance of those lists with CSS-in-JS arithmetic operations
  • Set z-index to 1 (sic, should be 0) even when redundant for discoverability

In the following table we compare just the differences between them on key attributes to better manage z-index conflicts in micro frontends.

Legend:

  • ✅: featured
  • ❌: not featured but still doable on your own
  • ⛔: inhibited

Our recommendation

The solution from Nguyen is the most interesting only if the following preconditions are met:

  1. It is feasible to implement the z-index promotion logic in all micro frontends
  2. It is entirely impossible that you are asked (via UX spec) to interleave overlays from two different micro frontends

For the general case, our recommendation is, instead of Nguyen’s, a union of the fundamental ideas from Balzer and Pomerantsev, with some additions (marked in italic).

  1. A shared list of z-index constants for the root stacking context only is kept in sync with the UX specification
  2. Every micro frontend consumes that shared list
  3. Each micro frontend creates its own stacking context and may have its own hierarchy of z-index lists
  4. Each micro frontend appends its flyovers (if any) into <body>, and gives them a fitting z-index from that shared list
  5. The container application sets the “ground-level” z-index for each micro frontend
  6. Optionally: A mutation observer observing just the children of <body> (subtree: false, childList: true, attributeFilter as needed), is put in place to detect when two flyovers are displayed simultaneously with the same z-index, and report it as warning/error to an application performance management backend

Graphically:

How our recommendation compares to the existing approaches

To provide the ability to implement any UX specification with moderate integration cost, our recommendation sacrifices vanilla DOM relationship.

However, logic required to cover this gap is provided by stable tools for leading frameworks such as Angular (Dynamic component loader), React (Portals), and Vue (Teleport).

Acknowledgements

Special thanks to Florin Senoner for his review and feedback.

Bonus: tools for debugging stacking context

Chrome extension “z-context” https://chrome.google.com/webstore/detail/z-context/jigamimbjojkdgnlldajknogfgncplbh?hl=en

Microsoft Edge devtools > “…” icon with hover text “Customize and control Dev tools” > “More tools” > 3D View > Z-index

  • Graphical
  • Terrible performance if for some reason edge://settings/system > “Use hardware acceleration when available” is switched off
  • Does not support shadow DOM as of version 89
  • Otherwise, supports as many scenarios as the rendering engine (excluding mask, mask-image, mask-border as of version 89)

Updated on May 13, 2021 with a more general explanation on the z-index example given, better links in our recommendation, and fixed Edge devtools guide.

--

--