Kebanyakan elemen di sebuah halaman duduk manis berdampingan atau bertumpuk ke bawah — mereka tidak saling menutupi. Tapi begitu kamu mulai membuat menu dropdown, dialog modal, tooltip, header yang menempel saat di-scroll, atau apa pun yang mengambang di atas konten lain, elemen mulai saling tumpang tindih. Dan saat dua elemen menempati titik yang sama, browser harus memutuskan mana yang sebenarnya kamu lihat di depan.
Keputusan itulah yang disebut urutan tumpukan (stacking order), dan z-index adalah cara kamu mengendalikannya. Property-nya kecil, tapi reputasinya bikin pusing — orang menyetel z-index: 9999 tapi tetap tidak ngefek, dan mereka tidak paham kenapa. Setelah selesai membaca artikel ini, kamu bakal paham persis bagaimana penumpukan bekerja, kenapa z-index kadang cuek sama perintahmu, dan bagaimana caranya berhenti berkelahi dengannya. Yuk kita mulai.
Dimensi ketiga: sumbu z
Sebuah halaman web punya dua dimensi yang jelas terlihat: mendatar (sumbu x) dan tegak (sumbu y). Nah, z-index mengurusi dimensi ketiga yang tak kasat mata — sumbu z, yang arahnya keluar dari layar menuju ke arahmu. Anggap saja ini soal kedalaman, atau “seberapa dekat sebuah elemen ke wajahmu”.
Saat elemen saling bertumpuk, yang posisinya lebih tinggi di sumbu z akan menutupi yang ada di belakangnya. Nilai z-index yang lebih besar berarti “lebih dekat ke penonton”, jadi dialah yang menang dan tampil di atas:
.behind {
z-index: 1;
}
.in-front {
z-index: 2; /* angka lebih besar menang — tampil di atas */
}
Itulah seluruh model berpikirnya: angka lebih besar, makin di depan. Masalahnya, z-index tidak bekerja di mana saja, dan tidak selalu bertingkah sesuai apa yang angkanya tunjukkan. Dua kejutan inilah yang sering bikin orang tersandung — jadi mari kita bereskan satu per satu.
Aturan pertama: z-index butuh positioning
Inilah jebakan yang hampir selalu menjebak orang di percobaan pertamanya. Secara bawaan, z-index tidak melakukan apa-apa pada elemen biasa yang posisinya static. Kamu bisa menyetel z-index: 9999 dan menyaksikannya sama sekali tanpa efek.
Supaya z-index mau bekerja, elemennya butuh nilai position selain bawaan static — artinya relative, absolute, fixed, atau sticky:
.box {
z-index: 5; /* diabaikan — position masih static */
}
.box {
position: relative; /* sekarang z-index baru boleh bekerja */
z-index: 5; /* nah, ini baru ngefek */
}
Jadi aturannya sederhana: z-index cuma berlaku untuk elemen yang punya posisi. Kalau z-index-mu kelihatan sama sekali tidak berpengaruh, inilah hal pertama yang harus kamu cek — sembilan dari sepuluh kasus, ternyata elemennya memang belum dikasih position.
Selalu pasangkan z-index dengan position
Setiap kali kamu menulis z-index, pastikan elemen yang sama punya position berupa relative, absolute, fixed, atau sticky. z-index tanpa positioning akan diabaikan diam-diam — tidak ada pesan error, dia cuma tidak melakukan apa-apa. Membiasakan menulis keduanya berbarengan bakal menyelamatkanmu dari banyak momen melongo ke layar tanpa tahu salahnya di mana.
Kalau kamu masih belum mantap soal beda position: relative, absolute, dan fixed, itu topik tersendiri yang layak dibaca terpisah — tapi untuk urusan z-index, yang perlu kamu tahu cuma satu: mana pun dari ketiganya akan mengeluarkan elemen dari status static sehingga z-index bisa bekerja.
Apa yang terjadi tanpa z-index sama sekali
Sebelum buru-buru pakai z-index, ada baiknya kamu paham dulu bagaimana browser menumpuk elemen ketika kamu tidak menyetelnya. Elemen yang saling tumpang tindih tetap punya urutan — z-index cuma alat untuk menimpa urutan itu.
Aturan bawaannya, secara garis besar, adalah urutan di kode (source order): elemen yang muncul belakangan di HTML akan menumpuk di atas elemen yang ditulis lebih dulu. Jadi kalau dua kotak yang sudah dipositioning saling tumpuk dan keduanya tidak punya z-index, yang ditulis lebih bawah di markup-lah yang menang:
<div class="box first">Pertama</div>
<div class="box second">Kedua</div>
.box {
position: absolute;
width: 150px;
height: 150px;
}
.first { top: 0; left: 0; background: tomato; }
.second { top: 40px; left: 40px; background: skyblue; }
Di sini .second menutupi .first dan duduk di atasnya — murni karena dia datang belakangan di HTML. Tanpa z-index sama sekali. Sering kali ini sudah persis seperti yang kamu mau, jadi kamu memang tidak butuh z-index. Kamu baru perlu menyentuhnya ketika urutan kode tidak memberikan susunan lapisan yang kamu inginkan.
Menentukan urutan tumpukan secara eksplisit
Sekarang mari kita benar-benar pakai z-index. Misalkan kamu punya tiga kartu yang saling tumpuk, dan kamu mau salah satu tertentu ada di depan, tak peduli urutannya di kode:
.card {
position: absolute;
}
.card-a { z-index: 1; }
.card-b { z-index: 3; } /* yang ini jadi paling depan */
.card-c { z-index: 2; }
Browser mengurutkannya berdasarkan z-index: card-b (3) paling depan, lalu card-c (2), lalu card-a (1) paling belakang. Angka aslinya tidak penting — yang penting cuma urutan relatifnya. z-index: 3 mengalahkan z-index: 2, sama persis seperti z-index: 300 mengalahkan z-index: 200. Tidak ada hadiah untuk angka besar.
Kamu juga bisa memakai nilai negatif untuk mendorong sebuah elemen ke belakang tetangga-tetangganya:
.background-shape {
position: absolute;
z-index: -1; /* duduk di belakang konten di sekitarnya */
}
Ini berguna untuk bentuk dekoratif atau grafis latar yang seharusnya berada di bawah teks dan tidak mengganggu klik.
Tahan refleks z-index: 9999
Saat sebuah lapisan tak mau maju ke depan, solusi yang menggoda adalah menempelkan angka raksasa seperti z-index: 9999. Kadang berhasil, tapi itu cuma tambal sulam: kamu akhirnya terjebak perlombaan angka di mana setiap komponen baru butuh angka yang lebih besar lagi, dan tak ada yang tahu nilai mana yang aman. Kebanyakan kasus, masalah aslinya bukan di angka sama sekali — melainkan stacking context, yang sebentar lagi kita bahas. Beresin itu, dan nilai z-index yang kecil dan masuk akal sudah lebih dari cukup.
Sumber kebingungan yang sesungguhnya: stacking context
Inilah konsep yang menjelaskan semua bug z-index yang bikin garuk-garuk kepala. Namanya stacking context, dan begitu kamu paham, z-index berhenti terasa seperti undian.
Stacking context adalah sekelompok elemen yang berdiri sendiri dan menumpuk bersama sebagai satu kesatuan. Inti pentingnya: nilai z-index hanya dibandingkan di dalam stacking context yang sama. Elemen di dalam satu context tidak akan pernah bisa melompat ke depan elemen di context lain yang lebih tinggi — sebesar apa pun z-index-nya.
Bayangkan saja seperti lantai di dalam sebuah gedung. Di dalam satu lantai, kamu bebas menata ulang perabotan sesukamu. Tapi kursi di lantai dua selalu berada di bawah apa pun yang ada di lantai tiga, bahkan kalau kursi itu kamu taruh di atas tumpukan yang sangat tinggi. Tumpukan tinggi itu (alias z-index-mu) hanya berlaku di lantainya sendiri.
Akar halaman (elemen <html>) adalah stacking context paling atas. Tapi — dan ini bagian krusialnya — banyak property CSS umum yang diam-diam membuat stacking context baru. Beberapa pemicunya yang biasa:
- Elemen dengan
positionyang disetel (relative/absolute/fixed/sticky) dan nilaiz-indexselainauto. position: fixedatauposition: sticky(keduanya membuat context sendiri begitu saja).opacityyang kurang dari1.- Nilai
transform,filter,perspective,clip-path, ataumask. will-changeyang menyebut salah satu property di atas.isolation: isolate(yang ini kita bahas di bawah — yang ini disengaja).
Daftar itulah penyebab z-index terasa seperti kena guna-guna. Kamu menyetel z-index anak jadi 9999, tapi induknya punya opacity: 0.99 atau sebuah transform, yang menjebak si anak di dalam stacking context berprioritas rendah. Si anak sekarang tidak bisa kabur melewati elemen-elemen di luar context itu, berapa pun angka yang kamu kasih.
Contoh nyata bug stacking context
Mari kita buat itu jadi konkret, karena inilah pusing kepala z-index yang paling sering muncul:
<div class="parent">
<div class="modal">Aku mau di atas segalanya!</div>
</div>
<div class="other-stuff">Konten lain</div>
.parent {
position: relative;
transform: translateY(0); /* ⚠️ ini membuat stacking context */
}
.modal {
position: absolute;
z-index: 9999; /* raksasa — tapi terjebak di dalam .parent */
}
.other-stuff {
position: relative;
z-index: 1;
}
Kamu pasti mengira .modal dengan z-index: 9999 bakal melibas .other-stuff yang z-index-nya cuma 1. Nyatanya tidak. transform di .parent membuat stacking context baru, sehingga seluruh rentang z-index milik .modal sekarang terkurung di dalam .parent. Dibandingkan dunia di luar, .parent secara keseluruhan menumpuk di level yang rendah — dan .modal terpaksa ikut terseret bersamanya.
Solusinya hampir tidak pernah berupa angka yang lebih besar. Yang benar, kamu menghapus property yang membuat context tak diinginkan itu, atau kamu menata ulang struktur supaya elemen yang harus di depan tidak terjebak di dalam context rendah — misalnya dengan memindahkan modal ke level paling atas halaman (pola yang umum dipakai untuk modal dan tooltip).
Membuat stacking context dengan sengaja: isolation
Ada kalanya kamu justru ingin stacking context baru — untuk memagari sebuah komponen supaya nilai z-index di dalamnya tidak bocor keluar dan bentrok dengan sisa halaman. Cara paling bersih dan paling jelas niatnya adalah property isolation:
.widget {
isolation: isolate; /* stacking context baru, tanpa efek samping */
}
Kenapa ini lebih enak daripada alternatif lain? Kamu memang bisa memaksa stacking context dengan opacity: 0.999 atau transform: translateZ(0), tapi itu trik akal-akalan — keduanya mengubah cara render, kadang bikin teks jadi buram, dan menunjukkan niat yang salah. isolation: isolate cuma melakukan satu hal: membuat stacking context, tanpa efek samping visual apa pun. Saat kamu membangun komponen yang bisa dipakai ulang dan ingin nilai z-index-nya rapi terkurung di dalam, inilah alatnya.
Sistem pelapisan yang praktis
Di proyek nyata, angka z-index yang berserakan sembarangan akan jadi mimpi buruk untuk dipelihara. Kebiasaan sederhana yang awet adalah mendefinisikan sekelompok kecil lapisan yang diberi nama, lalu konsisten memakainya — dan pakai CSS custom property biar gampang dibaca:
:root {
--z-base: 1;
--z-dropdown: 10;
--z-sticky: 20;
--z-overlay: 30;
--z-modal: 40;
--z-toast: 50;
}
.dropdown { z-index: var(--z-dropdown); }
.modal { z-index: var(--z-modal); }
.toast { z-index: var(--z-toast); }
Sekarang ada hierarki yang jelas dan terdokumentasi: toast mengalahkan modal, modal mengalahkan overlay, overlay mengalahkan header yang sticky, dan seterusnya. Tidak ada lagi yang perlu menebak-nebak apakah 47 lebih besar dari angka ajaib lain yang nyempil di tiga file berbeda. Saat butuh lapisan baru, tinggal sisipkan ke daftar di posisi yang tepat. Disiplin kecil ini membunuh sebagian besar kekacauan z-index sebelum sempat dimulai.
Sisakan jarak yang lega
Perhatikan, lapisannya naik dengan kelipatan sepuluh (10, 20, 30…), bukan satu per satu. Tujuannya supaya nanti ada ruang untuk menyelipkan lapisan baru di antara dua lapisan yang sudah ada tanpa harus menomori ulang semuanya. Alasannya mirip kenapa dulu orang menomori baris program BASIC dengan kelipatan sepuluh — ada ruang napas untuk sisipan.
Cara mendebug z-index yang keras kepala
Saat sebuah lapisan tak mau pindah ke tempat yang kamu mau, tahan dorongan untuk menaikkan angkanya. Lebih baik telusuri daftar pemeriksaan singkat berikut:
- Apakah elemennya sudah dipositioning? Kalau
position-nya masihstatic,z-indexdiabaikan. Inilah penyebab yang paling sering muncul. - Telusuri ke atas, ke para induknya. Cek tiap induk apakah punya
transform,filter,opacitydi bawah 1,position: fixed/sticky, atauwill-change. Salah satunya bisa membuat stacking context yang menjebak elemenmu. - Bandingkan dalam context yang sama. Dua nilai
z-indexbaru saling adu kalau berada di stacking context yang sama. Kalau tidak, yang dibandingkan adalah context-nya, bukan angkamu. - Pertimbangkan memindahkan elemennya. Untuk hal yang wajib ada di atas segalanya — modal, tooltip, dropdown — solusi yang paling kokoh sering kali adalah me-render-nya di level paling atas halaman, di luar context mana pun yang menjebak, ketimbang terus mengejar angka yang makin besar.
Developer tools di browser sangat membantu di sini: banyak di antaranya menampilkan stacking context dan membiarkanmu memeriksa z-index final dari elemen mana pun, yang mengubah tebak-tebakan jadi diagnosis cepat.
Penutup
z-index itu kecil tapi licin — dan hampir semua misterinya berasal dari satu konsep, yaitu stacking context. Berikut semua yang layak kamu ingat:
- Sumbu z mengarah keluar dari layar menuju ke arahmu;
z-indexyang lebih tinggi duduk lebih dekat ke penonton, jadi dia tampil di atas. z-indexcuma bekerja pada elemen yang dipositioning (relative,absolute,fixed, atausticky) — pada elemenstaticdia tidak melakukan apa-apa.- Tanpa
z-index, elemen yang bertumpuk disusun berdasarkan urutan di kode (yang lebih bawah di HTML menang). - Yang penting cuma urutan relatif nilai
z-index;9999tidak lebih kuat dari3kalau tidak ada apa pun di antaranya. - Stacking context adalah kelompok tumpukan yang berdiri sendiri. Nilai
z-indexcuma dibandingkan di dalam context yang sama — sebuah elemen tidak bisa kabur dari context-nya, setinggi apa pun angkanya. - Pemicu umum context baru:
transform,filter,opacity < 1,position: fixed/sticky, dan yang disengajaisolation: isolate. - Pakai sistem pelapisan kecil yang diberi nama dengan jarak yang lega ketimbang angka sembarangan, dan debug dengan mengecek positioning lebih dulu, lalu menelusuri ke atas ke para induk untuk mencari stacking context yang tersembunyi.
Setelah kamu akrab dengan stacking context, z-index berhenti jadi tebak-tebakan — kamu bakal langsung mengenali jebakannya, membenahi hal yang benar, lalu lanjut. Kalau kamu ingin menyegarkan lagi bagaimana positioning dan tumpang-tindih cocok ke gambaran besarnya, ada baiknya menengok kembali box model CSS dan alat tata letak yang sudah kamu kenal seperti Flexbox dan Grid — keduanya menentukan di mana elemen berada, sementara z-index menentukan siapa yang menang saat mereka saling menutupi.