D
P
0
← Semua artikel Read in English

Layout, Overflow & Cascade CSS

Semua `rem` Menyusut 19% Setelah `html { font-size: 13px }`? `10rem` Jadi 130px Karena `rem` Mengikuti Root

· · 6 menit baca
Semua `rem` Menyusut 19% Setelah `html { font-size: 13px }`? `10rem` Jadi 130px Karena `rem` Mengikuti Root

Spesifikasinya hanya satu kalimat: UI produk berukuran 13px dengan line-height 1.5. Kalimat itu datang pada 7 September 2026 lewat catatan merek dari pihak klien, diteruskan oleh pengembang backend mereka, untuk aplikasi web klien yang sedang saya kerjakan.

Saya menerjemahkannya dengan cara yang paling harfiah, yaitu html { font-size: 13px }, dan menurut catatan saya baris itu masuk ke kode aplikasi pada hari yang sama. Yang menangkapnya bukan saya, melainkan review otomatis, sebelum push. Sekilas barisnya tampak tidak berbahaya karena angkanya persis yang diminta. Masalahnya, rem dihitung terhadap elemen root, jadi baris yang sama ikut mengubah basis setiap rem di codebase.

rem membaca root, bukan teks

Catatan saya merumuskan kesalahannya dalam satu kalimat: spesifikasi 13px itu aturan tentang teks, bukan aturan tentang root. Sebelum perubahan itu, aplikasi ini tidak punya font-size di root sama sekali, jadi 1rem bernilai 16px bawaan browser. Aturan html di berkas gaya globalnya, src/styles/base.ts, hanya berisi font-family, box-sizing, dan line-height: 1. Begitu root diberi 13px, 1rem ikut menjadi 13px. Hitungannya, 13 dibagi 16 sama dengan 0,8125. Setiap panjang rem tinggal 81,25 persen dari ukuran semula, atau susut 18,75 persen, yang dibulatkan menjadi 19 persen.

Penyusutan itu mengenai 39 deklarasi rem di 12 berkas, dan tidak satu pun terlihat berhubungan dengan suntingan itu. Tiga di antaranya sudah cukup untuk melihat polanya:

berkas (sebelum perbaikan)      deklarasi             1rem = 16px   1rem = 13px
index.tsx, baris 1556           height: '10rem'       160px         130px
styledComponents.ts, baris 86   border-radius: 1rem   16px          13px
styledComponents.ts, baris 98   border-radius: 1rem   16px          13px

Deklarasi tinggi itu berupa gaya inline di JSX sebuah berkas komponen, dan diam-diam menjadi 130px, bukan 160px. Dua border-radius: 1rem di styledComponents.ts masing-masing menjadi 13px. CSS tidak melempar galat untuk perubahan seperti ini. Kotaknya hanya 30px lebih pendek dan sudut-sudutnya sedikit kurang membulat dibanding sebelumnya, jauh dari baris yang disunting.

Reset global yang meneruskan apa pun dari root

Catatan saya menandai satu faktor lagi yang memperparah keadaan. Berkas gaya global yang sama punya reset yang mewariskan lima properti sekaligus ke setiap elemen:

*, *::before, *::after {
  box-sizing: inherit;
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
}

font-size sebenarnya sudah diwariskan secara bawaan, jadi reset ini tidak menciptakan pewarisan. Yang ia hapus adalah pengecualiannya. Stylesheet bawaan browser memberi heading dan kontrol form ukuran sendiri, dan font-size: inherit di semua elemen menimpa pengecualian itu. Akibatnya, apa pun yang dibawa root menyebar ke setiap elemen yang tidak menetapkan ukurannya sendiri. Dengan 13px di html, satu nilai itu sekaligus menjadi basis 39 panjang rem dan ukuran teks bagi semua elemen yang tidak punya ukuran sendiri.

Screen pack sudah menunjukkan bentuknya

Jawabannya sebenarnya sudah tersedia sebelum baris itu ditulis. Screen pack desain yang menyertai pekerjaan ini sudah menunjukkan bentuk yang benar, dan saya tidak membacanya cukup teliti. Stylesheet-nya sama sekali tidak punya aturan untuk html. Aturan body-nya memakai 16px dengan line-height 1.6, dan pasangan 13px dengan line-height 1.5 baru muncul di kontainer .app:

/* stylesheet screen pack, disederhanakan ke properti yang relevan */
/* tidak ada aturan html sama sekali */
 
body {
  font-size: 16px;
  line-height: 1.6;
}
 
.app {
  font-size: 13px;
  line-height: 1.5;
}

Itu persis cara mengatur ukuran teks produk tanpa menyentuh rem. Root dibiarkan pada bawaan browser, sehingga 1rem tetap 16px, sementara angka 13px hanya berlaku di dalam kontainer aplikasinya.

html tanpa font-size, ukurannya pindah ke body

Aturan perbaikannya sederhana: ukuran produk ditaruh di body atau di kontainer aplikasi, dan html dibiarkan tanpa font-size. Di aplikasi ini pilihannya body. Commit perbaikan tanggal 7 September menaruh font-size: 13px dan line-height: 1.5 di aturan body. Commit yang sama mengganti font-family di html, membuang line-height: 1 dari sana, dan menyetel token fontSize di tema Ant Design, pustaka komponen yang dipakai aplikasi ini, menjadi 13 supaya komponennya mengikuti ukuran teks yang sama.

Pesan commit-nya mencatat alasannya secara eksplisit: pasangan itu sengaja ditaruh di body, bukan html, karena menaruhnya di html akan mengubah basis setiap rem dari 16px ke 13px dan diam-diam menyusutkan 39 panjang rem. Seluruh commit hanya menyentuh lima berkas, yaitu public/index.html dan empat berkas di src/styles. Dua berkas komponen yang memuat contoh di atas tidak termasuk. Perbaikannya bekerja dari sisi root, bukan dari sisi pemakai rem.

Commit itu terdorong ke branch remote pada hari yang sama. Riwayat git lokal tidak menyimpan satu pun commit yang menaruh font-size: 13px di html. Satu-satunya commit yang menambahkan string itu ke berkas gaya global adalah commit perbaikan tadi, dan di sana nilainya ada di body.

Per 1 Oktober keadaannya masih sama. Aturan html tetap tanpa font-size, dan body masih membawa 13px/1.5 bersama komentar yang menjelaskan alasannya:

/* src/styles/base.ts per 1 Oktober 2026, dipotong ke bagian yang relevan */
html {
  box-sizing: border-box;
}
 
/*
 * Product text is 13px with line-height 1.5, and it sits on body rather than
 * html on purpose. The pack does the same: it has no html rule at all and
 * scopes the pair to its .app container. Putting it on html would re-base
 * every rem in the app from 16px to 13px, which silently shrinks the 39 rem
 * lengths spread across the codebase.
 */
body {
  font-size: 13px;
  line-height: 1.5;
}

Menurut saya komentar itu sama pentingnya dengan perbaikannya. Bagi siapa pun yang tidak tahu ceritanya, memindahkan 13px dari body ke html akan terlihat seperti perapian yang wajar.

Ukur di browser, jangan percaya CSS-nya

Catatan saya juga mewajibkan satu langkah lagi, yaitu verifikasi di browser dengan mengukur elemen probe, bukan dengan mempercayai CSS-nya. Probe-nya sebuah div absolut di luar layar, selebar 1rem dan setinggi 10rem:

const p = document.createElement('div')
p.style.cssText = 'position:absolute;left:-9999px;width:1rem;height:10rem'
document.body.appendChild(p)
p.getBoundingClientRect()   // expect 16 and 160, not 13 and 130

Hasil yang benar adalah 16 dan 160, bukan 13 dan 130. Tinggi 10rem membuat selisihnya mudah terbaca, 30px dan bukan hanya 3px. Karena rem hanya membaca root, probe yang ditempel ke body tetap terukur 16 dan 160 walaupun body sudah membawa 13px. Membaca CSS saja tidak cukup, sebab nilai 1rem ditentukan oleh font-size terhitung di root, dan nilai itu bisa datang dari stylesheet mana pun yang dimuat halaman. Probe mengukur hasil akhirnya.

Probe yang sama tertanam di dua skrip ukur yang saya pakai untuk pekerjaan tema aplikasi ini. Yang satu menyimpan hasilnya sebagai remProbe, yang lain membawa komentar // rem probe: must stay 16/160.

Soal urutan buktinya, saya perlu jujur. Catatan hari kejadian hanya menyebut kesalahan itu tertangkap sebelum push, lalu memberi cara memasang dan mengukurnya. Hasil probe pada 7 September tidak tercatat di sana. Bukti ukur tersimpan yang paling awal berasal dari 21 September, ketika sensus sebelum pekerjaan tema global menyimpan "remProbe": [16, 160]. Pengukuran di browser keesokan harinya membaca 16/160 dengan nol galat halaman, probe tetap 16/160 sebelum dan sesudah perubahan tema itu, dan pengukuran tema tanggal 1 Oktober kembali mencatat 16/160 dengan nol galat halaman. Catatan tema global itu sendiri menetapkan aturan agar font-size tidak pernah ditaruh di html, dengan merujuk ke catatan bug ini.

Spesifikasi teks bukan perintah untuk root

Yang membuat kesalahan ini licin adalah angkanya benar. Ukuran 13px memang yang diminta, dan yang keliru hanya tempatnya. Spesifikasi seperti "UI produk 13px dengan line-height 1.5" bicara tentang teks, sedangkan font-size di html adalah keputusan tentang satuan yang dipakai seluruh codebase. Keduanya kebetulan ditulis dengan properti CSS yang sama.

Jadi kalau kamu menerima spesifikasi ukuran teks, taruh di body atau di kontainer aplikasi dan biarkan html tanpa font-size. Kalau spesifikasinya datang bersama berkas desain, baca stylesheet berkas itu dan lihat di elemen mana angkanya dipasang. Cek juga apakah ada reset yang mewariskan font-size ke semua elemen, karena reset seperti itu membuat apa pun yang ditaruh di root menyebar lebih jauh. Terakhir, ukur di browser dengan satu div selebar 1rem dan setinggi 10rem yang harus terbaca 16 dan 160.