Справочник · CSS
z-index: какой элемент окажется сверху
Свойство z-index задаёт порядок элементов по оси, направленной к зрителю: элемент с большим значением рисуется поверх элемента с меньшим. Оно работает у позиционированных элементов (position не static) и у детей flex- и grid-контейнеров. Сравниваются значения только внутри одного контекста наложения — поэтому z-index: 9999 иногда не помогает.
- Работает при
position: relative/absolute/fixed/stickyили у детей flex и grid. - Большее число — выше, но только внутри одного контекста наложения.
- Контекст создают transform, opacity меньше 1, position с z-index и другие.
- Ребёнок не поднимется выше соседа своего родителя, сколько ни увеличивай число.
- Держи короткую шкалу слоёв в переменных: 10, 20, 30.
Минимальный пример
:root {
--z-dropdown: 10;
--z-header: 20;
--z-modal: 30;
}
.header { position: sticky; top: 0; z-index: var(--z-header); }
.dropdown { position: absolute; z-index: var(--z-dropdown); }
.modal { position: fixed; inset: 0; z-index: var(--z-modal); }Шапка закрывает прокручиваемый контент, выпадающее меню — содержимое страницы, модальное окно — всё остальное. Короткая шкала в переменных понятнее, чем разбросанные 3, 999 и 100000.
Порядок без z-index
Без z-index браузер рисует элементы в порядке HTML: кто ниже в коде, тот выше на экране. Позиционированные элементы рисуются поверх обычных. Поэтому часто z-index не нужен вовсе: абсолютный бейдж внутри карточки и так окажется поверх её содержимого. z-index добавляют, когда этого порядка недостаточно.
Когда z-index работает
.badge { z-index: 5; } /* не работает: position static */
.badge { position: relative; z-index: 5; } /* работает */
.grid > .card:hover { z-index: 1; } /* работает у ребёнка grid без position */У обычного элемента с position: static z-index игнорируется. Достаточно position: relative без координат — элемент останется на месте, но z-index заработает. Исключение — дети flex- и grid-контейнеров: им position не нужен.
Контекст наложения
<div class="card"> <!-- transform: translateY(0) создаёт контекст -->
<div class="menu">…</div> <!-- z-index: 9999 -->
</div>
<div class="card">…</div> <!-- следующая карточка -->Контекст наложения — группа элементов, которая на странице сортируется как единое целое. Внутри неё дети упорядочиваются своими z-index, но наружу группа участвует одним значением — z-index самого элемента, создавшего контекст. Если у первой карточки есть transform, её меню с z-index 9999 всё равно окажется под второй карточкой: сравниваются карточки, а у них одинаковый уровень, и вторая идёт позже в HTML.
Что создаёт контекст наложения
| Условие | Пример |
|---|---|
| position absolute или relative и z-index не auto | position: relative; z-index: 1 |
| position fixed или sticky | всегда |
| ребёнок flex или grid с z-index не auto | z-index: 0 |
| opacity меньше 1 | opacity: .99 |
| transform, filter, perspective, clip-path, mask не none | transform: translateZ(0) |
| mix-blend-mode не normal | mix-blend-mode: multiply |
| isolation: isolate | специально для этого |
| will-change со свойством из списка | will-change: transform |
Чаще всего «z-index сломался» из-за transform или opacity у одного из предков: их добавили для анимации и не подумали о слоях.
Как починить наложение
/* вариант 1: поднять родителя, а не ребёнка */
.card:has(.menu.is-open) { position: relative; z-index: 10; }
/* вариант 2: убрать лишний transform у предка */
.card { transform: none; }
/* вариант 3: изолировать компонент, чтобы его слои не путались с чужими */
.widget { isolation: isolate; }Порядок поиска: выдели в DevTools оба элемента и поднимайся по предкам, проверяя у каждого transform, opacity, filter и z-index. Найди общего предка — сравниваются его ближайшие дети, в которых лежат твои элементы. Поднимай их, а не увеличивай число у глубоко вложенного элемента. Свойство isolation: isolate создаёт контекст без побочных эффектов — удобно, чтобы слои компонента не воевали со страницей.
Отрицательный z-index
.card { position: relative; isolation: isolate; }
.card::before {
content: "";
position: absolute;
inset: -4px;
z-index: -1; /* под содержимым карточки, но не под страницей */
background: linear-gradient(135deg, #2a62d9, #8b5cf6);
border-radius: 16px;
}Отрицательный z-index отправляет элемент ниже обычного содержимого. Без контекста у родителя такой псевдоэлемент может уйти под фон самой страницы и исчезнуть. isolation: isolate на карточке держит его внутри неё.
Верхний слой: dialog и popover
Модальное окно на теге dialog, открытое через showModal(), и элементы с атрибутом popover попадают в особый верхний слой браузера — он рисуется поверх всей страницы, и z-index на него не влияет. Это самый надёжный способ показать окно поверх всего, не сражаясь с контекстами наложения.
Увеличить число или поднять родителя
Найти, какой предок создал контекст, и изменить его уровень или убрать лишний transform. Решает причину.
Не помогает, если элементы в разных контекстах, и запускает гонку чисел по всему проекту.
Частые ошибки
- Ставят z-index элементу без position — он игнорируется.
- Увеличивают число до 99999, хотя элементы в разных контекстах наложения.
- Добавляют предку transform или opacity для анимации и ломают слои меню.
- Кладут модальное окно внутрь блока с низким контекстом вместо dialog.
- Используют отрицательный z-index без контекста у родителя, и элемент пропадает.
Доступность
Элемент, перекрытый другим слоем, может оставаться в фокусе клавиатуры: человек нажимает Tab и попадает на невидимую кнопку. Для модальных окон используй dialog с showModal() — браузер сам делает остальную страницу неактивной. Фиксированные шапки не должны закрывать элемент в фокусе: помогает scroll-padding-top на html.
Поддержка браузерами
z-index поддерживается всеми браузерами. isolation: isolate работает во всех актуальных браузерах, верхний слой для dialog и атрибут popover — в современных версиях Chrome, Safari и Firefox.
Практика
Собери две карточки подряд, у первой — выпадающее меню, которое должно перекрывать вторую карточку. Добавь первой карточке transform: translateY(0) и убедись, что меню ушло под вторую. Почини наложение, не увеличивая z-index меню.
Показать решение
.card { position: relative; transform: translateY(0); }
.card .menu { position: absolute; top: 100%; left: 0; z-index: 1; }
/* поднимаем карточку с открытым меню целиком */
.card:has(.menu:not([hidden])) { z-index: 2; }
/* без :has — класс на карточке: .card.is-menu-open { z-index: 2; } */Частые вопросы
Почему z-index не работает?
У элемента position: static (z-index игнорируется) или он находится в другом контексте наложения, чем элемент, который должен перекрыть.
Что такое контекст наложения?
Группа элементов, которая сортируется на странице как одно целое. Её создают, например, transform, opacity меньше 1 или position с z-index.
Почему меню с z-index 9999 под соседним блоком?
Его родитель создал контекст наложения. Сравниваются родитель и соседний блок — поднимай родителя.
Нужен ли position для z-index?
Да, любое значение кроме static. Исключение — дети flex- и grid-контейнеров.
Как показать окно поверх всего без z-index?
Используй тег dialog с методом showModal() или атрибут popover: они рисуются в верхнем слое браузера.