Depth levels and stacking contexts
One of the most challenging problems is managing the level of overlapping contexts. This page describes how we address this issue, what kind of solutions we came up with and what has to be addressed by application developers themselves.
We do not want to interfere with the layout of the consuming application, or at least as minimal as possible.
We want our components to behave deterministically and as close to the native elements.
Why is that?
Every time an element breaks out of the natural layout defined by the box model and potentially overlaps, one has to make sure that somehow the depth is correct.
This happens e.g. if:
- some kind of overlay is used (toast notifications, modals, message boxes),
- contents are attached additionally (drop downs, date pickers, tooltips),
- or if elements overlap above others (menubar, scrollbar, inline headings).
Some of them are addressed with our portal implementation, others may not. To develop a deterministic solution and to comply with the browsers defaults and the underlying specifications, we found three major use cases in our investigations.
Some background
The browser organizes the given contents with the DOM structurally, and derives a rendered representation of it using a deterministic layout model. And without going too deep into this huge topic, it must be said, that the level of depth is, like most of the other concerns, derived from an elements position in the DOM (the later the most on top).
Many "solutions" to this rely on introducing some explicit leveling using z-index
and tooling to orchestrate this in some way. As this seems to look fine on the first glimpse, it introduces some other issues, especially if the own application or component shares the viewport with foreign content.
Browsers organize the level of depth in stacking contexts. By default, the rendered document has a global context which can receive explicit positioning by z-index
and is implicitly leveled by the order of the appearance of the elements in the DOM. Introducing some kind of leveling within this global context is fine and solves most of the problems an average application is challenged with.
But there's more than one stacking context. More precisely there are uncountable stacking contexts, each organizing their contents in the same way and being themselves organized within their parent contexts as well. And as there are many possibilities how a stacking context can be introduced, it would be just not very reliable to manage levels within them.
Because every z-index
will only work within its respective context.
In contrast, the
tabindex
order is always preserved throughout a document, but it's a common misconception thatz-index
does, too.
Use cases
We investigated three major use cases in relation to our components and about their presence within an application:
Case A: Internal stacking contexts
tl;dr we use
isolation: isolate
internally
We have some components that need to manage the depth level of parts of their own light DOM. To scope this leveling techniques and be able to use explicit z-index
es internally as well, we introduce the isolation
property to our own elements. Thus, we'll explicitly create our own stacking context within our components.
So we do not interfere with the stacking context the component lives in, which is in application scope.
Example
Case B: Foreign stacking contexts
tl;dr application authors have to manage our components within their stack themselves
Some components may require positioning above others within the application. We understand that of not being in our scope, as we would intrude the foreign layout model. So it is up to the application author to manage the depth leveling within the application scope, but with our elements behaving just like the native ones do.
Example
Case C: Portal stacking context
tl;dr portal contents are managed by us, the latter is the highest
Everything regarding portals is using our internal API. We let all projected contents in a portal determine their level by the order in the DOM, aka. the last one added lies above. The whole portal is elevated to a specific (customizable) level. The overall level of the portal is exposed as --zui-portal-level
.
We may introduce some ordered portal levels to allow for prioritizing element types above or below others in the future.