</> Praktum

Главная / CSS / Свойство z-index

Справочник · CSS

z-index: какой элемент окажется сверху

Редакция PraktumОбновлено 7 мин чтенияУровень: новичок

Свойство 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 не autoposition: relative; z-index: 1
position fixed или stickyвсегда
ребёнок flex или grid с z-index не autoz-index: 0
opacity меньше 1opacity: .99
transform, filter, perspective, clip-path, mask не nonetransform: translateZ(0)
mix-blend-mode не normalmix-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: 999999

Не помогает, если элементы в разных контекстах, и запускает гонку чисел по всему проекту.

Частые ошибки

  • Ставят 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: они рисуются в верхнем слое браузера.

Источники

Ещё в разделе CSS