Dlaczego z-index nie działa?
- Czas potrzebny na przeczytanie:6 minut
- Opublikowane:
Niech pierwszy rzuci kamieniem ten, który nigdy nie miał problemów z z-index w CSS. Poprawne ułożenie elementów nie raz przyprawiło mnie o ból głowy. Masakra! Nic w tym dziwnego, ten mechanizm to jeden z "kruczków" CSS, które potrafią być naprawdę problematyczne.
Tworzysz prosty sklep e-commerce. Chciałbyś, żeby nawigacja była pod, a koszyk nad innymi elementami. Prościzna? Dopóki wszystko będzie szło po Twojej myśli. Zanim zaczniesz wyrywać włosy z głowy, z powodu niedziałającego z-index: 999999;, zrozum jak ten mechanizm naprawdę działa.

Podstawy
Wszyscy wiemy jak ustawić dwa kwadraty tak, aby jeden nachodził na drugi. Wrzucamy dwa boxy do wspólnego rodzica. Dominującemu kwadratowi nadajemy większą wartość z-index:
<style>
.wrapper {
position: relative;
}
.box-red {
position: absolute;
z-index: 2;
}
.box-pink {
position: absolute;
z-index: 1;
}
</style>
<div class="wrapper">
<div class="box-red"></div>
<div class="box-pink"></div>
</div>
Śmiga, można się rozejść. Rozwiązaliśmy problem upierdliwych z-index!

Hola hola, to jeszcze nie wszystko. Weźmy teraz na tapet sytuację, w której nasz dominujący kwadrat, opakowujemy w dodatkowy wrapper:

Wszystko poszło się je... Wróćmy do kodu i przeanalizujmy co mogło pójść nie tak.
<div class="wrapper">
<div class="box-dashed">
<div class="box-red"></div>
</div>
<div class="box-pink"></div>
</div>
Na pierwszy ogień idzie HTML - od teraz nasz dominujący kwadrat jest opleciony w dodatkowego rodzica. Z perspektywy CSS, nasz rodzic co prawda ma mniejszy z-index od kwadratu box-pink, ale dominujący box-red powinien to przebić.
Gdzie więc leży problem? 🤔
.wrapper {
position: relative;
}
.box-dashed {
position: relative;
z-index: 1;
}
.box-red {
position: absolute;
z-index: 3;
}
.box-pink {
position: absolute;
z-index: 2;
}
Podpowiedź: ustawienie z-index na 999999999 nie pomoże 🙈
Stacking Context
Winowajcą tego całego zamieszania jest mistyczny mechanizm w CSS zwany stacking context.
Domyślnie, czysty dokument HTML tworzy context względem którego układa elementy. O stacking context możemy myśleć jak o warstwie, która pozwala nam układać elemeny względem siebie.
Poza domyślą warstwą, nadając elementom konkretne właściwości, tworzymy nowy stacking context. Dla przykładu - połączenie position: relative / position: absolute + z-index tworzy nową warstwę.

Porównywać ze sobą elementy możemy tylko względem nadrzędnej warstwy. Właściwość z-index nie jest właściwością globalną!
Tworząc dwa elementy, gdzie każdy element posiada position: relative + odpowiedni z-index, tworzymy dwie warstwy. Obie warstwy posiadają w sobie dodatkowo po trzy elementy. Każdy z zagnieżdżonych elementów posiada własny z-index + position: absolute.
Wizualizując sobie ten mechanizm, możemy myśleć o zagnieżdżonych warstwach, jak o wersjach nadrzędnego stacking context. Wersja 1.5 jest większa od 1.3, ale nie ma szans na przebicie 2.4. Nie ważne jak duży z-index damy, mógłby on nawet wynosić 999999, a i tak nie przebije wersji 2.0:

Spróbujmy naprawić nasz poprzedni przykład. Zależy nam na tym, żeby kwadrat z klasą box-red pojawił się nad box-pink. Osiągamy to dzięki pozbyciu się warstwy, którą tworzył box-dashed za pomocą dwóch wcześniej wspomnianych właściwości - position: relative + z-index.
Chcemy nadal pozycjonować odpowiednio elementy, więc zostawiamy position: relative. Usuwamy za to z-index, który w tej kombinacji jest niezbędny do stworzenia warstwy.
/* Stacking Context: ❌ */
.wrapper {
position: relative;
}
/* Stacking Context: ❌ */
.box-dashed {
position: relative;
/* z-index: 1; */
}
/* Stacking Context: ✅ */
.box-red {
position: absolute;
z-index: 3;
}
/* Stacking Context: ✅ */
.box-pink {
position: absolute;
z-index: 2;
}
Usuwając stacking context box-dashed zostajemy z trzema warstwami:
- Dokument HTML
box-redbox-pink
Zagnieżdżone w domyślym stacking context dokumentu HTML, warstwy box-red oraz box-pink pozostają na tym samym poziomie i mogą być porównane ze sobą.

Co tworzy Stacking Context?
Warstwy możemy tworzyć na wiele różnych sposóbów. Powiedzieliśmy sobie, że połącznenie position: relative / position: abosolute + z-index tworzy nowy stacking context, czy jest coś jeszcze?
Z popularniejszych metod możemy wymienić:
- Dokument HTML
- Element z pozycją
relative/abosolute+z-index(inne niżauto) - Element z pozycją
sticky/fixed - Dziecko elementu, który ma
displayustawiony naflex/grid - Element z
opacitymniejszym niż1
Całą listę znajdziesz w dokumentacji MDN.
Kolejność ułożenia w Stacking Context
Udało nam się stworzyć nową warstwę, ale nadal coś nie działa? 🤬
Elementy znajdujące się w obrębie danej warstwy układane będą względem danej kolejności:
- Wypozycjonowane elementy z negatywnym
z-index - Elementy z
position: static - Wypozycjonowane elementy z
z-index: auto - Wypozycjonowane elementy z pozytywnym lub zerowym
z-index

Praktyczny przykład
Wróćmy do naszego początkowego problemu. W naszym sklepie nawigacja ma się znajdować pod, a koszyk nad resztą elementów.

Tworzymy odpowiednią strukturę HTML i nadajemy elementom odpowiednie z-index. Elementowi z klasą .main przypisujemy większy z-index, niż elementowi z klasą .header. Header jest rodzicem koszyka, więc dodatkowo nadajemy mu position: relative - dzięki temu będziemy mogli swobodnie umiejscowić koszyk za pomocą position: absolute.
<style>
.header {
position: relative;
z-index: 1;
}
.cart {
position: abosolute;
top: 5rem;
right: 5rem;
z-index: 999999;
}
.main {
position: relative;
z-index: 10;
transform: translateY(-50px);
}
</style>
<body>
<header class="header">
<nav></nav>
<div class="cart"></div>
</header>
<main class="main">
<aside></aside>
<ul></ul>
</main>
</body>
Widzisz problem? 🤔
Element z klasą .header stworzył nowy stacking context. Aby poprawić wygląd naszego sklepu, potrzebujemy pozbyć się nowo utworzonej warsty. Kasujemy zbędny z-index: 1 z header i wyszystko zaczęło śmigać!
Od teraz warstwy koszyka i elementu z klasą <main> są na tym samym poziomie - dlatego możemy je bez obaw z sobą porównywać.
.header {
position: relative;
}
.cart {
position: absolute;
top: 5rem;
right: 5rem;
z-index: 2;
}
.main {
position: relative;
z-index: 1;
transform: translateY(-50px);
}
Uprościłem również wartości z-index - dbaj o swoje z-indexy i nie nadawaj im nieskończonych wręcz wartości. Zobacz jak utrzymywać w porządku swoje z-index - Managing CSS Z-Index In Large Projects

Warstwy w izolacji
Wyobraź sobie sytuację, w której tworzysz jakiś reużywaly kawałek strony. W naszym przypadku niech to będzie kafelek przedmiotu. Występuje on na stronie głównej, ale wyświetla się również na stronie z daną kategorią. Od dziś każda taka karta będzie miała w tle grafikę SVG.
Potrzebujemy wypozycjonować SVG tak, żeby znajdowało się pod resztą elementów. Jazda, dodajemy negatywny z-index i gotowe! Nie do końca, nasza grafika zniknęła z pola naszego widzenia - znajduje się pod samą kartą.

Jeśli pomyślałeś o stacking context, to masz w 100% racje, musimy go stworzyć!
Mamy naprawdę mnóstwo metod, dzięki którym stworzymy nową warstę. Problem jest tylko taki, że karta ma być reużywalna. Nie chcemy jej wprost podać z-index, bo nie wiemy jak będzie się zachowywała na innych stronach.
Całe na biało w takich przypadkach wjeżdża isolation: isolate w CSS. Pozwala nam ono stworzyć nowy stacking context bez żadnych skutków ubocznych. Świetna alternatywa dla innych sposóbów, które w przeciwieństwie do isolation, posiadają nieporządane efekty :)

Podsumowanie
Ustawianie elementów za pomocą z-index potrafi być naprawdę upierdliwe... Zrozumienie konceptu stacking context to podstawa do uniknięca drogich wizyt u psychologa.
Do usłyszenia!

