Что такое «Ад z-index» и когда он появляется?
Обычно «адом z-index» в фронтенде называют ситуацию, когда на проекте хаотично и без системы раскиданы значения z-index, из‑за чего всё начинает перекрывать всё, а поведение слоёв становится непредсказуемым и плохо управляемым. Это не формальный термин из спецификации, а жаргон, которым разработчики описывают накопившийся бардак с контекстами наложения и слоями интерфейса.
Что именно имеют в виду

- На странице десятки или сотни элементов с z-index: 1, 10, 99, 9999, 2147483647 и т.п., выставленных «на глаз», без единой шкалы и правил.
- Элементы внезапно перестают перекрывать друг друга так, как ожидается, из‑за контекстов наложения (position, opacity, transform и т.д.), и для «починки» начинают ещё сильнее поднимать z-index.
- В итоге любой новый попап, модалка или тултип требуют «подбор значения, которое перебьёт всё остальное», а изменить верстку, не сломав старые слои, становится почти нереально.
Как это выглядит на практике

Типичные признаки «ада z-index»:
- У модалок и оверлеев огромные значения z-index просто потому, что «иначе не работает».
- В разных компонентах используются свои независимые шкалы слоёв, не согласованные между собой.
- Любая правка layout’а может внезапно «утопить» важные элементы под другими слоями, и отладка превращается в охоту за невидимыми контекстами наложения.
Как от этого обычно спасаются
- Вводят общую шкалу слоёв: например, базовые элементы, хедер, сайдбар, попапы, модалки, нотификации — каждый в своём диапазоне.
- Описывают эти уровни в токенах/переменных (SASS, CSS custom properties, дизайн‑система), вместо «магических чисел» по месту.
- Минимизируют создание новых контекстов наложения и аккуратно используют свойства, которые их создают (position, opacity < 1, transform, filter, etc.).
По сути, «ад z-index» — это не про само свойство, а про отсутствие архитектуры слоёв и дисциплины в верстке: когда z-index превращается из простого инструмента в источник бесконечных костылей.
Какие свойства влияют на создание контекста z index
Контекст наложения создаётся не только z-index, а целым набором свойств, которые «отрывают» элемент от общего стека и делают для него свой локальный слой. Ниже — список основных условий.
Базовые случаи
- Корневой элемент документа
<html>всегда создаёт корневой контекст наложения - Любой элемент с позиционированием
positionнеstaticи сz-index, отличным отauto, создаёт новый контекст наложения (relative,absolute,fixed,sticky+z-index: 0/1/...)
Свойства, создающие новый контекст
Новый stacking context создают, даже без z-index:
opacity < 1(любое значение строго меньше 1)- Любое ненулевое
transform(transform: translate(...),scale(...),rotate(...)и т.п.) - Любой
filter(blur,drop-shadow,brightnessи т.д.) will-change, если в нём указаны свойства, создающие композитный слой (transform,opacityи др.)mix-blend-mode,isolation: isolate,clip-path,mask/mask-image,perspectiveтоже могут порождать собственный контекст наложения
Дополнительные случаи
- Элементы со
position: fixedиposition: stickyобычно ведут себя как корни собственных контекстов наложения - Некоторые свойства в сочетании (например,
transform+positionилиfilter+opacity) гарантированно создают отдельный стек, из-за чего внутреннийz-indexуже не может «перебить» элементы снаружи
Итоговая памятка
Чаще всего контекст наложения появляется, когда:
- Есть непустой
z-indexу непозиционированного по умолчанию элемента. - Включены эффекты, которые заставляют браузер делать отдельный «слой отрисовки»:
opacity < 1,transform,filter,will-change,clip-pathи похожие.

Краткая шпаргалка по контекстам наложения (z-index)
Основные случаи
| Условие / свойство | Создаёт контекст наложения? | Комментарий / типичные грабли |
|---|---|---|
html (корень документа) | Да | Всегда корневой контекст, всё внутри него. |
position ≠ static + z-index != auto | Да | Классика: relative/absolute/fixed/sticky + z-index: 0/1/.... Внутренний z-index уже не перебивает внешние элементы. |
Часто встречающиеся свойства
| Свойство / условие | Создаёт контекст? | Что важно помнить |
|---|---|---|
opacity < 1 | Да | Любое значение строго меньше 1. Модалки/оверлеи с полупрозрачностью могут «запереть» детей внутри своего контекста. |
Любой transform | Да | translate/scale/rotate/... создают новый контекст. Частая причина, почему элемент «не может» перекрыть фиксированный хедер. |
Любой filter | Да | Любой фильтр (blur, drop-shadow, brightness и т.п.) делает свой локальный стек. |
will-change: transform/opacity/... | Да | Подготовка к анимации создаёт отдельный слой, даже если эффект ещё не применён. |
mix-blend-mode != normal | Да | Элемент смешивает слои и одновременно становится корнем собственного контекста. |
isolation: isolate | Да | Принудительно создаёт новый контекст для элемента. Удобно, чтобы «обрубить» влияние внешних слоёв. |
clip-path | Да | Любая обтравка тоже отрывает элемент в отдельный контекст. |
Маски (mask, mask-image) | Да | Аналогично clip-path, всё внутри живёт в своём стеке. |
perspective на элементе | Да | Если не на transform-style: preserve-3d предке, может создать новый контекст для потомков. |
Позиционирование и специальные случаи
| Свойство / ситуация | Создаёт контекст? | Комментарий |
|---|---|---|
position: fixed | Часто да | В большинстве реализаций считается корнем собственного контекста; ведёт себя поверх обычного потока, но может быть «заперт» transform-родителем. |
position: sticky | Часто да | Ведёт себя как корень контекста в зоне прилипания; взаимодействует с родительскими контекстами. |
Flex-контейнер (display: flex) | Нет сам по себе | Но его дети с z-index и/или position участвуют в своём порядке наложения; доп. свойства могут создать контекст. |
Grid-контейнер (display: grid) | Нет сам по себе | Аналогично flex: контекст появится при свойствах из таблиц выше. |
Если что‑то «не перекрывается как надо», почти всегда виноват новый контекст наложения. Ниже — короткий чеклист.
Когда попап / тултип не перекрывает
- Проверь родителя на
transform/opacity/filter
- Если у любого предка стоит
transform,opacity < 1,filter,will-change,clip-path,mix-blend-mode,isolation: isolate— он создаёт новый контекст, и z-index ребёнка не может перебить элементы снаружи. - Лечение: убрать эффект с предка, поднять наверх DOM‑дерева, либо повесить попап ближе к
body(портал).
- Убедись, что z-index задан правильно
- У элемента с
z-indexдолжен бытьpositionнеstatic; иначе значение игнорируется. - Если у родителя
z-index: auto, но при этом на нём создан контекст (например,opacity < 1), то его дети не «пробьют» соседний контекст вообще, как бы ты ни крутил их z-index.
Когда шапка/оверлей «прячут» всё остальное
- Проверь, не слишком ли рано создан «верхний» контекст
- Частая ошибка: на самом верхнем контейнере (
.app,.wrapper) висятtransformилиfilter(из-за анимаций, скролла и т.п.), и весь интерфейс оказывается в одном внутреннем контексте, который ниже фиксированной шапки/оверлея. - Лечение: вынести «глобальные» слои (хедер, модалки, тосты) за пределы этого контейнера или убрать с него свойства, создающие контекст.
- Не завышай бессистемно z-index
- Если приходится докручивать до 9999 и выше — проблемма не в числе, а в структуре контекстов.
- Введи шкалу уровней (например: базовый 0–10, шапка 100, сайдбар 200, попапы 1000, модалки 2000, алерты 3000) и зафиксируй её в переменных/токенах.
Ещё пару правил на каждый день
- Минимизируй создание новых контекстов
- Перед тем как повесить
transform/filter/opacityна крупный контейнер (layout, карточки, списки) — подумай, не нужно ли будет потом показывать поверх него попапы/хинты. - Часто лучше анимировать внутренний элемент, а не весь контейнер, чтобы не ломать стек наложения вокруг.
- Отладка по шагам в DevTools
- Включай инспектор, смотри computed‑стили: есть ли
transform,opacity < 1,filter,position+z-indexу предков. - Если элемент «проигрывает» в наложении, сравни контексты: кто выше по дереву, у кого собственный стек, и на каком уровне реально сравниваются
z-index.