Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «CSS z-index та контекст накладення». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**CSS z-index** встановлює порядок накладання позиціонованих елементів по осі Z. Більше значення означає «вище», але лише в межах одного контексту накладання (stacking context). ```css .parent { position: relative; z-index: 1; } /* Контекст накладання */ .child { position: absolute; z-index: 100; } /* Замкнений всередині parent */ .other { position: relative; z-index: 2; } /* Перемагає parent і child */ ``` **Головне:** дочірній елемент не може вийти з контексту свого батька. Child з z-index: 100 програє сусідньому елементу з z-index: 2, якщо їхні батьки в різних контекстах.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**CSS z-index** - встановлює порядок накладання позиціонованих елементів по осі Z. Але це порівняння відбувається лише всередині контексту накладання (stacking context), локального контейнера, який ізолює дочірні елементи від решти сторінки. ## Теорія ### TL;DR - z-index порівнює лише елементи в одному контексті накладання, а не по всій сторінці - Дочірній елемент з z-index: 9999 всередині батька з z-index: 1 програє будь-якому елементу з z-index: 2 зовні - Контексти накладання створюють: `position` + не-auto `z-index`, `opacity < 1`, `transform`, `filter`, `isolation: isolate` та інші - Уяви це як ліфтові шахти зі скла: елементи всередині однієї шахти ніколи напряму не перекривають елементи з іншої - Проблеми зі стеком вирішуй через `isolation: isolate`, а не збільшенням чисел ### Швидкий приклад ```css .parent { position: relative; z-index: 5; } /* Створює контекст накладання */ .child { position: absolute; z-index: 100; } /* Замкнений всередині parent */ .other { position: absolute; z-index: 6; } /* Виграє — порівнюється з parent z=5, а не child z=100 */ ``` У child є z-index: 100, але він все одно під `.other`. Браузер порівнює `.parent` (z=5) з `.other` (z=6). Значення child у цьому змаганні не враховується. Воно конкурує лише всередині `.parent`. ### Як працюють контексти накладання Коли браузер малює сторінку, він сортує елементи за z-order кореневого елемента їхнього контексту накладання, а не за кожним індивідуальним z-index. Кожен контекст малюється як єдина плоска група. Браузер не заглядає всередину. Контекст з z-index: 1 повністю відрисовується перед контекстом з z-index: 2, незалежно від того, які значення є всередині. Кореневий `<html>` починає перший контекст накладання. Властивості на кшталт `position: relative; z-index: 1` рекурсивно запускають нові. Chrome (рушій Blink) відстежує кожен контекст як об'єкт `PaintLayer` для GPU-композитингу. ### Що створює контекст накладання | Властивість / Умова | Приклад | | --- | --- | | Кореневий елемент | `<html>` | | `position` + `z-index` (не auto) | `position: relative; z-index: 1` | | `opacity` < 1 | `opacity: 0.99` | | `transform` (будь-яке значення) | `transform: translateX(0)` | | `filter` (будь-яке значення) | `filter: blur(0)` | | `will-change` | `will-change: transform` | | `isolation: isolate` | Явно створює контекст | | `position: fixed` або `sticky` | Завжди створює контекст | | Дочірній flex/grid елемент з `z-index` | `z-index: 1` на flex-дочірньому елементі | Найчастіший сюрприз — `opacity: 0.99`. Додав його для плавного fade-ефекту, і він автоматично створює контекст накладання. Тултіп всередині раптово ховається за сайдбаром без жодної очевидної причини. ### Коли і як використовувати - **Модальне вікно або drawer**: `position: fixed; z-index: 1000` на рівні кореня. У React використовуй `ReactDOM.createPortal(modal, document.body)`, щоб вийти з будь-якого батьківського контексту. - **Вкладений dropdown**: застосуй `isolation: isolate` до батьківського компонента, щоб внутрішні z-index значення не виходили назовні. - **Картки, що перекриваються**: `position: relative; z-index: auto` на сусідніх елементах — без створення нових контекстів. - **Уникай великих чисел**: замість `z-index: 99999` оголоси шкалу через CSS custom properties. ```css :root { --z-dropdown: 100; --z-modal: 400; --z-tooltip: 600; } .modal { position: fixed; z-index: var(--z-modal); } .tooltip { position: fixed; z-index: var(--z-tooltip); } ``` ### Типові помилки **z-index на статичному елементі** ```css .box { z-index: 10; } /* Не працює */ ``` z-index ігнорується, якщо `position` не є `relative`, `absolute`, `fixed` або `sticky`, або елемент не є flex/grid-дочірнім. Додай `position: relative` і все запрацює. **Великий z-index замкнений у низькому контексті** ```css .parent { opacity: 0.99; } /* Створює контекст накладання */ .tooltip { position: absolute; z-index: 9999; } /* Застряг всередині parent */ ``` `opacity` запускає новий контекст. Будь-що зовні з z-index: 1 виграє. Перенеси тултіп до кореневого контейнера або підніми z-index батька. Саме цю помилку шукають найдовше в реальних проектах: перевіряєш z-index, збільшуєш, перевіряєш знову — нічого не міняється, бо проблема три рівні вище в DOM. **Від'ємний z-index ховає елемент за фоном** ```css .overlay { position: absolute; z-index: -1; } ``` Від'ємні значення розміщують елемент під фоном батька. Це рідко те, що потрібно. Якщо треба обрізати шар без z-index трюків, використай `isolation: isolate` на батьку. **Плутанина між flex order і z-index** ```css .container { display: flex; } .item1 { order: 2; z-index: 10; position: relative; } .item2 { order: 1; z-index: 1; position: relative; transform: translateX(0); } ``` `.item2` опиняється зверху попри z-index: 1. `transform` виводить його в окремий шар композитингу. Багато розробників очікують, що DOM-порядок і візуальний порядок збігаються. Вони не збігаються, коли є compositing layers. ### Реальне використання - **React Portal**: `ReactDOM.createPortal(modal, document.body)` розміщує модал у кореневому контексті накладання, без пасток батьківських елементів - **TailwindCSS**: утиліта `z-50` на `fixed` елементах у Next.js застосунках працює за тим самим принципом - **Bootstrap**: `.modal-backdrop` використовує `z-index: 1040` всередині контексту, створеного через `opacity: 0.5` на елементі фону - **Сучасна альтернатива**: псевдоелемент `::backdrop` і Popover API обробляють оверлеї без ручного z-index стекінгу в браузерах, що їх підтримують ### Питання на співбесіді **Q:** У чому різниця між `z-index: auto` і `z-index: 0`? **A:** `auto` означає, що елемент бере участь у батьківському контексті накладання без створення нового. `0` створює новий контекст накладання на нульовому рівні, що змінює поведінку дочірніх елементів. **Q:** Мій модал має z-index: 9999, але ховається за навбаром. Чому? **A:** Навбар або один з його предків має `transform` чи `filter`, що створює вищий контекст накладання. Перевіряй computed styles кожного предка аж до `<body>` і шукай ці властивості. **Q:** Як `will-change: transform` впливає на накладання? **A:** Він виводить елемент на GPU-шар композитингу і створює контекст накладання ще до початку анімації. Це запобігає jank під час переходів, але може стати несподіванкою, якщо забудеш, що контекст вже створено заздалегідь. **Q:** Чи є випадок, коли `z-index: auto` і `z-index: 0` поводяться однаково? **A:** Так, коли елемент не є позиціонованим і не є flex/grid-дочірнім. У такому випадку обидва значення нічого не роблять. Різниця з'являється лише там, де елемент міг би створити контекст накладання. **Q:** (Senior) Чи працює z-index через межу Shadow DOM? **A:** Ні. Shadow root створює контекст накладання, і z-index не пробивається через тіньову межу. Для крос-граничного накладання в Chrome 89+ можна використовувати `::part()` або slotted елементи. ## Приклади ### Базовий порядок накладання ```css .card-a { position: relative; z-index: 1; background: lightblue; } .card-b { position: relative; z-index: 2; /* Відображається над card-a */ background: coral; } ``` Два сусідніх елементи з `position: relative`, обидва в кореневому контексті накладання. Їхні z-index значення порівнюються напряму. Вище значення виграє. Це простий випадок, який працює саме так, як очікується. ### Модал над картками дашборду (React Portal) ```jsx // Без порталу модал замкнений у батьківському контексті з z-index: 1 function Dashboard() { return ( <div style={{ position: 'relative', zIndex: 1 }}> <Card style={{ position: 'relative', zIndex: 10 }}> Картка дашборду </Card> {ReactDOM.createPortal( <div style={{ position: 'fixed', inset: 0, zIndex: 1000 }}> Вміст модалу {/* Тепер у кореневому контексті, перемагає все */} </div>, document.body )} </div> ); } ``` Без порталу модал знаходиться всередині `div` з `z-index: 1`. Навіть з `z-index: 1000` він не переможе сусідній елемент з `z-index: 2` зовні. Портал переміщує його до `document.body`, розміщуючи в кореневому контексті, де порівняння відбувається напряму. ### Пастка transform ```html <div class="sidebar" style="position: relative; z-index: 10; transform: translateX(0);"> <!-- transform створює контекст накладання --> <div class="tooltip" style="position: absolute; z-index: 9999;"> Цей тултіп у пастці всередині sidebar </div> </div> <div class="header" style="position: relative; z-index: 11;"> Header перемагає тултіп попри його z=9999 </div> ``` `transform` сайдбара створює контекст накладання. z-index: 9999 тултіпа конкурує лише всередині сайдбара. Header порівнюється з сайдбаром (z=10), а не з тултіпом (z=9999), і виграє з z=11. Приберіть `transform` з сайдбара або збільште його z-index — і тултіп знову видно.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.