mitmario.dev

z-index und der Stacking Context

CSS Im Browser 5 Min Lesezeit 4 BeispieleLektion 6 von 8

Sobald sich zwei Elemente überlappen, muss der Browser entscheiden, welches oben liegt. Er tut das nach festen Regeln, und die erste davon kommt ohne z-index aus.

Erst die Reihenfolge, dann die Position

Ohne z-index gewinnt die Reihenfolge
.feld {
  position: relative;
  height: 220px;
  font-family: system-ui, sans-serif;
}

/* Alle drei sind positioniert und haben keinen z-index.
   Es entscheidet allein die Reihenfolge im Dokument. */
.kasten {
  position: absolute;
  width: 200px;
  padding: 16px;
  color: #ffffff;
}

.a {
  top: 0;
  left: 0;
  background-color: #2563eb;
}

.b {
  top: 40px;
  left: 40px;
  background-color: #15803d;
}

.c {
  top: 80px;
  left: 80px;
  background-color: #b91c1c;
}

Ohne weitere Angaben liegt oben, was später im Dokument steht. Im Beispiel sind alle drei Kästen gleich aufgebaut, und der letzte deckt die beiden anderen ab.

Dazu kommt eine zweite Regel. Positionierte Elemente liegen über nicht positionierten. Ein Element mit position: relative liegt also über einem statischen Nachbarn, selbst wenn dieser später im Markup steht.

Das erklärt schon einen guten Teil der Fälle, in denen etwas überraschend oben oder unten liegt. Bevor du zu z-index greifst, lohnt deshalb die Frage, ob nicht einfach die Reihenfolge im HTML die falsche ist.

z-index braucht eine Position

z-index an einem statischen Element
.feld {
  position: relative;
  height: 180px;
  font-family: system-ui, sans-serif;
}

.kasten {
  width: 240px;
  padding: 16px;
  color: #ffffff;
}

/* Ein hoher z-index an einem statischen Element wirkt
   nicht. Die Eigenschaft gilt nur für positionierte
   Elemente sowie für Flex- und Grid-Kinder. */
.statisch {
  z-index: 999;
  background-color: #b91c1c;
}

/* Positionierte Elemente liegen ohnehin über
   nicht positionierten. */
.positioniert {
  position: relative;
  top: -40px;
  left: 40px;
  background-color: #15803d;
}

z-index nimmt eine ganze Zahl, auch negative. Es wirkt nur auf positionierte Elemente und auf die Kinder eines Flex- oder Grid-Containers, bei denen auch ohne position.

An einem statischen Element ist die Angabe wirkungslos. Sie ist gültig, sie wird nicht verworfen, und sie tut nichts.

Im Streifen siehst du das am Unterschied zwischen zwei Reitern. Am roten Kasten steht im Reiter „Regeln“ .statisch mit z-index: 999, nicht durchgestrichen und ohne Vermerk. Im Reiter „Werte“ gibt es dagegen gar keine z-index-Zeile, denn die steht dort nur bei Elementen, die auch positioniert sind. Am grünen Kasten daneben steht sie sehr wohl, mit dem Wert auto. Und dieser Kasten liegt oben, obwohl er gar keine Zahl hat.

Bewirkt ein z-index: 999 nichts, frage ich zuerst, ob das Element überhaupt positioniert ist. Die Antwort steht im Streifen in der Zeile position.

Zu den Zahlen selbst noch ein Wort. 999 und 9999 sind eine Kapitulation. Wer solche Werte vergibt, weiß nicht mehr, wogegen er anstinkt, und der Nächste muss dann 99999 nehmen. Eine kleine Skala mit drei bis vier Stufen, einmal oben in der Datei notiert, hält jahrelang.

Der Stacking Context

Gefangen im Stacking Context
.feld {
  position: relative;
  height: 200px;
  margin-bottom: 16px;
  font-family: system-ui, sans-serif;
}

/* opacity unter 1 öffnet einen Stacking Context.
   Alles darin wird zusammen als eine Ebene einsortiert,
   und die hat selbst gar keinen z-index. */
.gruppe {
  position: relative;
  opacity: 0.99;
}

.kind {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 9999;
  width: 220px;
  padding: 16px;
  color: #ffffff;
  background-color: #b91c1c;
}

.nachbar {
  position: relative;
  z-index: 1;
  /* 20px und nicht mehr: .gruppe ist null Pixel hoch,
     weil ihr einziges Kind absolut positioniert ist.
     Der Nachbar startet deshalb ganz oben, und bei
     einem größeren Wert überlappen die beiden
     kaum noch. Zu sehen wäre dann nichts. */
  top: 20px;
  left: 60px;
  width: 220px;
  padding: 16px;
  color: #ffffff;
  background-color: #15803d;
}

Bestimmte Eigenschaften öffnen einen Stacking Context, also einen eigenen kleinen Stapel. Alles darin wird untereinander sortiert und danach als ein einziges Element in den umgebenden Stapel einsortiert.

Was daraus folgt, siehst du im Beispiel. Das Kind hat z-index: 9999, der Nachbar draußen nur z-index: 1, und trotzdem liegt der Nachbar oben. Nach außen zählt nicht die 9999, sondern der Platz der Gruppe, und die hat selbst gar keinen z-index.

Nachlesen lässt sich das an beiden. Im Streifen steht am Kind z-index: 9999 und am Nachbarn z-index: 1. Beide Zahlen sind echt, sie treten nur nie gegeneinander an. Die Gruppe selbst lässt sich nicht antippen, sie ist nur eine Hülle ohne eigene Höhe. Ihre Regel steht trotzdem im Reiter „Regeln“ des Kindes, blass und mit „Selektor trifft nicht“, denn sie trifft die Gruppe und nicht das Kind. Darin steht opacity: 0.99, und diese Zeile öffnet den Stacking Context.

Diese Angaben öffnen unter anderem einen Stacking Context.

  • position: relative oder absolute zusammen mit einem z-index ungleich auto, der bekannte Fall. Ein Flex- oder Grid-Kind mit z-index gehört auch dazu, auch ohne position.
  • position: fixed und sticky, immer, auch ganz ohne z-index. Das trifft die klebende Kopfzeile.
  • opacity unter 1, der unbekannte Fall, und deshalb steht er im Beispiel. Machst du ein Element halb durchsichtig, ändert sich die Reihenfolge der Ebenen für alles darin.
  • transform samt translate, rotate und scale, dazu filter, backdrop-filter, perspective, clip-path, mask, mix-blend-mode, contain und isolation: isolate. Ein will-change gehört dazu, sobald es eine dieser Eigenschaften oder opacity ankündigt.

transform samt Verwandten, filter, backdrop-filter, perspective und will-change: transform sind dieselben, mit denen in Lektion 8.4 ein fixiertes Element plötzlich mitscrollte. Beides hat denselben Grund. Ein solches Element macht ein eigenes Koordinatensystem und einen eigenen Stacking Context auf.

Deckungsgleich sind die beiden Listen aber nicht. Der Unterschied geht nur in eine Richtung. opacity, mix-blend-mode, clip-path, mask und isolation öffnen nur einen Stacking Context und machen keinen Bezugsrahmen für fixed. Umgekehrt öffnet alles, was einen Bezugsrahmen für ein fixiertes Element aufmacht, auch einen Stacking Context.

isolation: isolate als Werkzeug

Bisher klang der Stacking Context nach einem Ärgernis. Er ist aber ebenso oft die Lösung.

isolation: isolate öffnet einen Stacking Context, ohne sonst irgendetwas zu ändern. Keine Position, keine Transparenz, keine Verschiebung. Für genau diesen Zweck gibt es die Eigenschaft.

So lässt sich eine Komponente einkapseln. Die z-index-Werte in einer isolierten Kartenliste können niemals mit der Kopfzeile der Seite konkurrieren, ganz egal, welche Zahlen jemand später innerhalb einer einzelnen Karte vergibt. Die Liste als Ganzes hat einen Platz in der Reihenfolge, und alles Innere bleibt darin.

isolation kapselt die Liste
body {
  font-family: system-ui, sans-serif;
}

.kopf {
  position: relative;
  z-index: 5;
  padding: 16px;
  font-weight: 700;
  color: #ffffff;
  background-color: #18181b;
}

.liste {
  margin: 8px 0 48px 0;
  padding: 0;
  list-style: none;
}

.karte {
  position: relative;
  padding: 16px;
  border: 1px solid #d4d4d8;
  border-radius: 12px;
}

/* Ragt 20px nach oben, also bis in die Kopfzeile. */
.badge {
  position: absolute;
  top: -20px;
  right: 16px;
  z-index: 10;
  padding: 4px 8px;
  font-size: 14px;
  color: #ffffff;
  background-color: #2563eb;
  border-radius: 8px;
}

.isoliert {
  isolation: isolate;
}

Im Beispiel ragt das Badge einer Karte mit z-index: 10 in die Kopfzeile, die nur z-index: 5 hat. Im oberen Teil liegt es deshalb über ihr. Im unteren steht an der Liste isolation: isolate, und jetzt liegt die Kopfzeile oben, ohne dass sich an einer der beiden Zahlen etwas geändert hat.

Das ist der Unterschied zwischen einem Stylesheet, in dem die Zahlen mit der Zeit wachsen, und einem, in dem sie klein bleiben. Isolierte Komponenten brauchen nur 1, 2 und 3, weil sie nur gegen ihre eigenen Geschwister antreten. Nur die Zahlen der Seite selbst liegen weiter auseinander, etwa 10 und 100, damit dort später noch etwas dazwischen passt.

Wenn der z-index nicht wirkt

Liegt etwas nicht oben, obwohl der z-index hoch ist, gehe ich diese drei Fragen von oben nach unten durch.

  1. Ist das Element positioniert? Ohne Position wirkt z-index nicht.
  2. Öffnet ein Vorfahre einen Stacking Context? Gesucht wird den Baum hinauf nach opacity, transform, filter und den anderen aus der Liste. Die ersten beiden stehen im Reiter „Werte“, sobald sie gesetzt sind, alle anderen nur im Reiter „Regeln“. Gibt es so einen Vorfahren, ist das Element darin gefangen, und der Wert gehört an den Vorfahren.
  3. Konkurrieren die beiden überhaupt im selben Stacking Context? Zwei Elemente in verschiedenen Stacking Contexts vergleichen ihre eigenen Zahlen nie miteinander. Es zählt der Platz ihrer jeweiligen Gruppen.

In der Aufgabe liegen vier Ebenen einander im Weg, und alle Positionen stehen schon.

Zum Mitnehmen

z-index ordnet nur innerhalb eines Stacking Context. Kommt ein Element trotz hoher Zahl nicht nach oben, hat meist ein Vorfahre einen eigenen geöffnet. Mit isolation: isolate öffnest du selbst einen, wenn eine Komponente unter sich bleiben soll.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.