Di sebuah custom theme klien, saya butuh satu section dua kolom: kolom kiri penuh konten (paragraf panjang, gambar), kolom kanan lebih ringan (cuma beberapa item kecil). Permintaannya sederhana di kepala, susah di CSS: kedua kolom harus sama tinggi, dan item di kolom yang ringan harus menyebar mengisi tinggi itu, bukan menumpuk di atas dan menyisakan ruang kosong di bawah.
Percobaan pertama saya naif. Saya kasih konten dalam kolom ringan max-height: 100% berharap dia "ikut" tinggi kolom kiri:
.col-light {
max-height: 100%;
}
.col-light .inner {
max-height: 100%;
}Hasilnya: konten malah collapse. Tingginya nggak naik, item tetap menumpuk di atas. Tidak ada error di console, hanya layout yang terlihat "salah" dan bikin saya garuk-garuk kepala.
Kenapa ini terjadi
max-height: 100% itu persen relatif terhadap tinggi parent. Masalahnya, di sini parent tingginya tidak ditentukan secara eksplisit, dia ditentukan oleh kontennya sendiri. Jadi terbentuk dependensi melingkar: tinggi anak bergantung pada tinggi parent, sementara tinggi parent bergantung pada tinggi anak. CSS tidak punya angka konkret untuk diselesaikan, jadi persen itu diabaikan dan elemen collapse ke tinggi natural-nya. Itu kenapa nilainya seolah tidak berefek apa-apa.
Solusi kedua saya lebih buruk secara prinsip: ukur dan paksa pakai JavaScript. Ambil offsetHeight kolom kiri, terus set tinggi kolom kanan dengan angka itu.
function syncHeights() {
const left = document.querySelector('.col-heavy');
const right = document.querySelector('.col-light');
right.style.height = left.offsetHeight + 'px';
}
window.addEventListener('resize', syncHeights);
syncHeights();Secara teknis ini "jalan", tapi rapuh. Dia telat saat resize, kelihatan ngejar layout sepersekian detik setelah viewport berubah. Dia juga berkelahi dengan layout shift, kalau ada font yang baru ke-load atau gambar yang baru selesai, tinggi yang sudah saya hardcode jadi salah sampai event resize berikutnya. Mengukur tinggi di JavaScript untuk hal yang sebenarnya tugas CSS itu hampir selalu pertanda saya melawan arus.
Perbaikannya
Kuncinya: pisahkan dua masalah yang selama ini saya campur. Masalah pertama, "buat kedua kolom sama tinggi" itu tugas Grid. Masalah kedua, "sebarkan anak di sisi ringan ke seluruh tinggi" itu tugas Flexbox. Masing-masing punya alat bawaan untuk itu, dan keduanya tanpa satu baris JavaScript pun.
CSS Grid secara default memakai align-items: stretch. Artinya, setiap grid item otomatis di-stretch mengisi tinggi penuh row-nya. Kalau dua kolom ada di row yang sama, row itu setinggi konten tertinggi, dan kedua item ikut setinggi itu. Jadi tinggi sama sudah gratis, cukup pakai Grid:
.row {
display: grid;
grid-template-columns: 1fr 1fr;
}Sekarang kolom ringan sudah punya tinggi konkret yang ter-stretch, ini yang tadi tidak dimiliki oleh max-height: 100%. Tinggal sebarkan anak-anaknya. Di sinilah Flexbox masuk: jadikan kolom ringan sebagai flex container vertikal, lalu pakai justify-content: space-between supaya anak-anaknya menyebar dari atas ke bawah mengisi seluruh tinggi:
.col-light {
display: flex;
flex-direction: column;
justify-content: space-between;
}Selesai. Item pertama nempel di atas, item terakhir nempel di bawah, sisanya terbagi rata di antaranya. Tidak ada pengukuran, tidak ada offsetHeight, tidak ada listener resize. Dan yang paling penting: dia reflow dengan benar saat resize, karena semuanya dihitung oleh layout engine di frame yang sama, bukan dikejar oleh JavaScript satu frame di belakang.
Satu catatan kecil yang sering bikin orang bingung: align-items: stretch baru jalan kalau item itu sendiri tidak punya tinggi eksplisit. Jadi jangan kasih height ke kolomnya. Biarkan Grid yang menentukan, dan biarkan flex di dalam yang membagi. Kalau saya butuh jarak minimum antar item alih-alih space-between murni, gap di flex container juga tetap menghormati distribusi ini.
Hasil akhir di browser persis seperti yang diminta klien: kolom kanan yang isinya cuma tiga item kecil kini menyebar rapi sepanjang tinggi kolom kiri yang penuh, dan saat layar dikecilkan semuanya menyusun ulang sendiri tanpa kedip.
Pelajaran
Saat dua kolom harus sama tinggi dan salah satunya perlu mengisi tinggi itu, jangan ukur apa pun di JavaScript. Bagi tugasnya: pakai stretch bawaan Grid untuk menyamakan tinggi kolom, lalu pakai flex column dengan justify-content: space-between di sisi yang ringan untuk membagikan anak-anaknya ke seluruh tinggi yang sudah ter-stretch itu. max-height: 100% gagal karena butuh tinggi parent yang konkret, yang tidak pernah ada dalam dependensi melingkar; Grid memberi tinggi konkret itu, dan Flexbox memanfaatkannya. Dua alat layout yang masing-masing mengerjakan satu hal yang memang dirancang untuknya, selalu mengalahkan satu skrip yang mencoba menebak tinggi.
