D
P
0

CSS & Web Animation

`overflow-x: hidden` Mematikan `position: sticky` di iOS Safari — Ganti ke `overflow-x: clip`

26 Juni 2026·4 menit baca
`overflow-x: hidden` Mematikan `position: sticky` di iOS Safari — Ganti ke `overflow-x: clip`

Sticky header di sebuah custom theme yang saya bangun jalan mulus di Chrome, Firefox, bahkan Safari desktop. Lalu klien membuka situsnya di iPhone, dan header yang harusnya nempel di atas saat scroll itu... cuma diam. Ikut ke-scroll begitu saja, seolah position: sticky-nya tidak pernah ada. Begitu juga rail samping yang harusnya tetap menempel. Tidak ada error di konsol, tidak ada warning, tidak ada apa-apa. Hanya iOS Safari yang berpura-pura sticky itu nilai yang tidak dikenal.

Yang bikin pusing: kodenya jelas-jelas benar. Elemennya punya position: sticky dan top: 0, nilai yang valid, dan di semua browser lain dia nempel sempurna.

.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
}

Saya sampai pinjam HP teman buat memastikan ini bukan satu device aneh. Sama saja di semua iPhone. Setelah beberapa jam membandingkan branch yang masih jalan dengan yang rusak, pelakunya ketemu — dan dia ada di tempat yang sama sekali tidak saya curigai. Bukan di header-nya. Tapi di body.

Beberapa hari sebelumnya saya menambal masalah lain: ada horizontal scroll tipis, mungkin satu-dua piksel, gara-gara sebuah elemen yang sedikit melebar dari viewport. Solusi refleks yang semua orang tahu:

body {
  overflow-x: hidden;
}

Scroll horizontalnya hilang. Saya pikir kasus ditutup. Ternyata baris itulah yang diam-diam mematikan sticky di iOS.

Kenapa ini terjadi

Inti masalahnya ada di apa yang sebenarnya dilakukan overflow: hidden di balik layar. Kita cenderung menganggap overflow-x: hidden sekadar "potong apapun yang melebar ke samping". Padahal lebih dari itu: nilai overflow selain visible — termasuk hidden, auto, dan scroll — menjadikan elemen tersebut sebuah scroll container.

Begitu sebuah ancestor jadi scroll container, dia mengubah konteks scroll untuk semua keturunannya. position: sticky itu bekerja relatif terhadap nearest scrolling ancestor-nya — di situlah dia menghitung kapan harus mulai "nempel". Saat saya menaruh overflow-x: hidden di body, saya tanpa sadar memindahkan scroll container yang relevan bagi si header. Containing block dan konteks scroll-nya berubah.

Di banyak browser desktop, perubahan ini diserap dengan mulus dan sticky tetap jalan. Tapi iOS Safari memperlakukannya berbeda: ketika sebuah ancestor menjadi scroll container lewat overflow: hidden, perilaku sticky pada keturunannya seringkali langsung mati. Tidak ada error, tidak ada fallback yang kelihatan — sticky cuma berhenti nempel dan berperilaku seperti elemen biasa.

Makanya debugging-nya menyesatkan. CSS si header tidak salah sedikit pun. Yang salah adalah satu properti pada ancestor jauh di atasnya, yang ditulis untuk alasan yang sama sekali tidak berhubungan, beberapa hari sebelumnya.

Perbaikannya

Solusinya satu kata: clip, bukan hidden.

body {
  overflow-x: clip;
}

overflow-x: clip memotong overflow horizontal persis seperti hidden — scroll satu-dua piksel yang mengganggu itu tetap hilang. Bedanya krusial: clip tidak menjadikan elemen sebagai scroll container. Dia cuma memotong apa yang menonjol keluar, tanpa membuat region yang bisa di-scroll, tanpa mengubah scrolling ancestor bagi keturunannya.

Karena tidak ada scroll container baru yang terbentuk, konteks scroll si header tetap utuh. position: sticky kembali punya referensi yang benar, dan di iOS Safari dia langsung nempel lagi seperti seharusnya. Satu kata diganti, dua masalah beres sekaligus: overflow tetap terpotong, sticky tetap hidup.

Satu hal yang perlu diperhatikan: dukungan clip di browser lawas sedikit di bawah hidden. Tapi di browser modern dia sudah didukung luas, jadi untuk kebanyakan proyek ini bukan masalah. Kalau Anda butuh jarak potong tertentu — misalnya ingin sedikit ruang sebelum konten benar-benar terpotong — pasangkan dengan overflow-clip-margin:

body {
  overflow-x: clip;
  overflow-clip-margin: 1rem;
}

Kalau Anda masih harus mendukung browser yang benar-benar tua dan tidak bisa pakai clip, alternatifnya adalah mencari sumber overflow-nya langsung — cari elemen yang melebar lalu beri batas lebar atau perbaiki marginnya — daripada menutupinya dengan overflow: hidden di ancestor yang berisi elemen sticky.

Pelajaran

clip bukan sinonim dari hidden. Keduanya sama-sama menyembunyikan overflow secara visual, tapi hidden punya efek samping yang tidak kelihatan: dia membuat scroll container, dan scroll container itu bisa menjebol position: sticky, scroll snapping, dan konteks posisi lain — terutama di iOS Safari yang paling rewel soal ini.

Aturan praktis yang saya pegang sekarang: kalau yang saya mau cuma memotong overflow dan bukan membuat area yang bisa di-scroll, saya pakai overflow: clip. overflow: hidden saya simpan untuk kasus di mana saya memang sengaja ingin sebuah scroll container, atau ketika menonaktifkan scroll secara eksplisit. Sejak membedakan dua niat itu, kategori bug "kok sticky-nya mati cuma di iPhone" ini hilang dari hidup saya. Dan kalau ada sesuatu yang patah hanya di iOS Safari, hal pertama yang saya periksa bukan elemen yang patah — tapi overflow di setiap ancestor di atasnya.