z-index решает, какой из перекрывающихся элементов окажется сверху - но это не просто число: работает он только на позиционированных элементах, а сравнение значений происходит не по всему документу, а внутри одного контекста наложения. Из-за этого больший z-index иногда неожиданно проигрывает меньшему.
В этом уроке
Попробуй прямо сейчас: поменяй у box-a значение z-index на 3 - синий блок окажется поверх красного, хотя в коде идёт раньше и в разметке ничего не менялось, кроме одного числа.
Порядок наложения по умолчанию: кто сверху без z-index
Без единого z-index браузер решает, кто сверху, не только по порядку в HTML. Главное правило: позиционированный элемент (position: relative, absolute, fixed или sticky) всегда рисуется поверх непозиционированного соседа - даже если в разметке идёт раньше него. Как уже говорилось в уроке про основы position, position: static полностью выключает элемент из игры с z-index - такой элемент всегда останется в самом низу, независимо от порядка в коде. А вот если оба элемента позиционированы и ни у одного нет z-index (оба со значением по умолчанию auto), тогда действительно решает порядок в HTML - сверху окажется тот, что идёт позже.
.card { position: relative; } - даже без z-index одного этого достаточно, чтобы поднять элемент над непозиционированными соседями.
ожидание, что порядок в HTML сам решает, кто сверху - неверно, если хотя бы один из перекрывающихся элементов позиционирован, а другой нет.
z-index и контекст наложения: почему больший иногда проигрывает
z-index работает только на позиционированных элементах: чем больше значение, тем выше слой. Но сравнение чисел происходит не по всему документу сразу, а внутри одного КОНТЕКСТА НАЛОЖЕНИЯ. Как только позиционированный элемент получает z-index, отличный от auto, он создаёт собственный, отдельный контекст - и все его потомки сравниваются друг с другом только внутри этого контекста, не участвуя напрямую в сравнении с элементами снаружи. Контекст наложения создаёт не только сочетание position + z-index: то же самое делают opacity меньше 1, а также transform, filter, perspective или will-change со значением, отличным от none - это ровно те же свойства, которые в уроке про absolute превращали даже статичного предка в точку отсчёта для потомков.
Отсюда классическая ловушка: пусть у контейнера A задан position: relative; z-index: 1, а глубоко внутри него - элемент с z-index: 9999. У соседнего контейнера B - position: relative; z-index: 2, а внутри - самый обычный элемент с z-index: 1. При перекрытии победит элемент из контейнера B, а не 9999 из A - потому что сначала сравниваются сами контейнеры (2 против 1), и только после этого - их содержимое. Число 9999 внутри A вообще не попадает в сравнение с содержимым B, ведь они находятся в разных контекстах наложения.
.modal { position: fixed; z-index: 1000; } - явный z-index на самом верхнеуровневом контейнере, а не на глубоко вложенных элементах внутри него.
.deep-child { z-index: 9999; } - огромное число глубоко внутри вложенного элемента не спасёт, если у самого контейнера z-index ниже, чем у соседнего контекста.
Отрицательный z-index: элемент "позади" - но позади чего именно
z-index может быть отрицательным - типичный приём для декоративного фона позади текста. Но здесь есть неочевидная ловушка: если у контейнера задан только position: relative, а собственный z-index НЕ задан, контейнер сам не образует контекст наложения. Тогда его потомок с отрицательным z-index "убегает" в контекст более высокого уровня - и может оказаться полностью скрытым за собственным фоном контейнера, хотя по логике должен быть только чуть позади содержимого. Чтобы отрицательный z-index вёл себя предсказуемо, самому контейнеру нужен явный z-index (подойдёт даже z-index: 0) вместе с position - тогда контейнер образует собственный контекст, и отрицательный потомок аккуратно ложится между фоном контейнера и обычным содержимым, как и ожидается.
.container { position: relative; z-index: 0; } вместе с .decor { position: absolute; z-index: -1; } внутри - надёжно работающий приём "фон позади контента".
.container { position: relative; } без z-index, а внутри .decor { z-index: -1; } - decor рискует полностью исчезнуть за фоном самого контейнера.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Задают z-index элементу без position | z-index полностью игнорируется браузером | Сначала задать position: relative/absolute/fixed/sticky - только потом z-index |
| Пишут z-index: 9999 в глубоко вложенном элементе | Проигрывает, если у его контейнера z-index ниже, чем у соседнего контекста наложения | Поднимать z-index у самого контейнера, а не только у элемента глубоко внутри |
| Дают контейнеру только position: relative без z-index, а потомку - отрицательный z-index | Потомок может полностью скрыться за фоном контейнера | Задать контейнеру явный z-index (например, 0), чтобы он образовал собственный контекст |
| Полагаются только на порядок в HTML при перекрытии | Порядок решает лишь среди элементов без z-index на одном уровне - в остальных случаях решают position и контекст наложения | Учитывать одновременно position, z-index и контекст наложения, а не только порядок разметки |
Шпаргалка
| Что нужно | Как сделать |
|---|---|
| Поднять элемент над непозиционированными соседями | position: relative (даже без z-index) |
| Явно задать порядок среди позиционированных элементов | z-index: число (чем больше - тем выше) |
| Создать собственный контекст наложения | position ≠ static + z-index ≠ auto (или opacity < 1, transform/filter/will-change ≠ none) |
| Спрятать элемент позади содержимого контейнера предсказуемо | отрицательный z-index у потомка + явный z-index у самого контейнера |
| z-index на непозиционированном элементе | не работает вообще - браузер его игнорирует |
Коротко: отвечаем на вопросы
Почему z-index не работает на моём элементе?
Скорее всего, у элемента не задан position (или он равен static). z-index действует только на позиционированных элементах - relative, absolute, fixed или sticky. На элементе с position: static он полностью игнорируется браузером.
Почему больший z-index иногда всё равно проигрывает меньшему?
Сравнение z-index происходит только внутри одного контекста наложения. Если элемент с большим z-index находится внутри контейнера с более низким z-index, чем у соседнего контейнера, он проиграет содержимому этого соседа - потому что сначала сравниваются сами контейнеры, а не глубоко вложенные значения.
Можно ли сделать z-index отрицательным?
Да, отрицательные значения используют, чтобы спрятать элемент позади обычного содержимого контейнера - например, для декоративного фона. Но чтобы это работало предсказуемо, у самого контейнера должен быть явно задан свой z-index (не только position) - иначе потомок может полностью скрыться за фоном контейнера.