Kenapa ini penting
Menulis CSS yang "jalan" itu mudah. Menulis CSS yang masih bisa dirawat setelah enam bulan, oleh lima orang berbeda, dengan ratusan komponen, itu tantangan sebenarnya. CSS punya satu sifat berbahaya: semua selector bersifat global. Aturan .judul yang kamu tulis untuk halaman blog bisa diam-diam merusak .judul di halaman checkout. Lama-lama tim jadi takut menghapus CSS apa pun ("jangan-jangan masih dipakai"), file membengkak, dan !important bertebaran.
Modul ini adalah sesi code review. Kamu akan belajar konvensi penamaan BEM, cara mengorganisasi file, peran reset atau normalize, lalu mempraktikkannya dengan merapikan CSS yang berantakan. Anggap ini seperti merapikan dapur: bumbu diberi label, alat dikelompokkan, dan semua orang tahu di mana mencari sesuatu.
Konsep inti
Gejala CSS yang perlu di-review
Saat me-review kode CSS (milikmu sendiri atau orang lain), cari tanda-tanda ini:
| Gejala | Kenapa bermasalah |
|---|---|
Selector panjang seperti .page .content div ul li a |
Specificity tinggi, rapuh saat struktur HTML berubah |
Banyak !important |
Tanda perang specificity |
| Styling berbasis ID | Sulit ditimpa dan tidak bisa dipakai ulang |
Nilai "ajaib" berulang (#3b82f6 muncul 40 kali) |
Ganti warna merek berarti ubah 40 tempat |
Nama class berdasarkan tampilan (.merah, .kiri-20) |
Saat desain berubah, nama jadi bohong |
| Satu file 5.000 baris | Sulit dicari, rawan konflik saat kerja tim |
BEM: Block, Element, Modifier
BEM adalah konvensi penamaan class yang membuat setiap komponen punya "namespace" sendiri.
- Block: komponen mandiri. Contoh:
.kartu,.menu,.form-login. - Element: bagian dari block, ditulis dengan dua garis bawah. Contoh:
.kartu__judul,.kartu__gambar. - Modifier: variasi atau keadaan, ditulis dengan dua strip. Contoh:
.kartu--unggulan,.tombol--besar,.menu__item--aktif.
<article class="kartu kartu--unggulan">
<img class="kartu__gambar" src="..." alt="...">
<h3 class="kartu__judul">Kopi Aren</h3>
<button class="tombol tombol--utama">Beli</button>
</article>
Keuntungan BEM:
- Specificity datar: hampir semua selector hanya satu class (0,1,0), jadi jarang ada perang specificity.
- Nama menjelaskan relasi: dari
.kartu__judulkamu langsung tahu elemen ini milik block kartu. - Aman dari tabrakan:
.kartu__judultidak akan bentrok dengan.modal__judul.
Aturan praktis: jangan membuat "cucu" seperti .kartu__badan__judul. Element selalu langsung milik block, walaupun di HTML posisinya bersarang dalam. Kalau sebuah bagian sudah cukup kompleks untuk punya anak-anak sendiri, jadikan block baru.
BEM bukan satu-satunya cara. Ada pendekatan utility-first (seperti Tailwind CSS) dan CSS Modules di React yang otomatis membuat nama class unik. Namun memahami BEM membantumu berpikir dalam komponen, apa pun alat yang nanti kamu pakai.
Reset vs normalize
Setiap browser punya gaya bawaan (user agent stylesheet): margin di body, ukuran heading, bullet di list. Gaya bawaan ini sedikit berbeda antar browser.
- Reset menghapus hampir semua gaya bawaan supaya kamu mulai dari kanvas kosong.
- Normalize (misalnya normalize.css) menyamakan perbedaan antar browser sambil mempertahankan gaya bawaan yang berguna.
Proyek modern biasanya memakai reset modern yang ringkas: beberapa baris yang memperbaiki hal paling mengganggu tanpa menghapus semuanya:
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
body { line-height: 1.5; -webkit-font-smoothing: antialiased; }
img, picture, video, canvas, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; }
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
Organisasi file
Urutan yang sehat bergerak dari yang paling umum ke yang paling spesifik (dikenal juga sebagai pola ITCSS, "segitiga terbalik"):
- Tokens / settings: custom properties warna, spacing, font.
- Reset / base: reset dan gaya elemen dasar (
body,a,h1). - Layout: kerangka halaman (
.container,.grid, header, footer). - Components: block BEM (
.kartu,.tombol,.modal). - Utilities: class kecil satu tugas (
.sr-only,.text-center) yang memang boleh menang.
CSS modern menyediakan @layer untuk mengunci urutan ini. Aturan di layer yang dideklarasikan belakangan selalu menang atas layer sebelumnya, berapa pun specificity-nya:
@layer reset, base, layout, components, utilities;
Contoh kode
Studi kasus: CSS warisan dari proyek lama.
/* SEBELUM: berantakan */
#main .content div.box { background: #fff; border: 1px solid #ddd; padding: 20px; }
#main .content div.box h3 { color: #3b82f6; font-size: 20px; margin: 0 0 10px 0; }
#main .content div.box p { color: #666; }
.blue { color: #3b82f6 !important; }
.box.special { border: 2px solid #3b82f6; }
#main .content div.box a { background: #3b82f6; color: white; padding: 10px 20px; display: inline-block; }
#main .content div.box a:hover { background: #2563eb; }
Masalahnya: specificity (1,2,2) dan lebih, warna #3b82f6 diulang empat kali, !important, class .blue yang namanya berdasarkan tampilan, dan .box tidak bisa dipakai di luar #main .content.
/* SESUDAH: rapi, terorganisasi, BEM + custom properties + @layer */
@layer reset, tokens, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
img { display: block; max-width: 100%; }
button, input { font: inherit; }
}
@layer tokens {
:root {
--warna-utama: #3b82f6;
--warna-utama-gelap: #2563eb;
--warna-teks-redup: #666;
--warna-garis: #ddd;
--ruang-2: 0.5rem;
--ruang-3: 0.75rem;
--ruang-5: 1.25rem;
}
}
@layer components {
.kartu {
padding: var(--ruang-5);
background: #fff;
border: 1px solid var(--warna-garis);
}
.kartu--unggulan { border: 2px solid var(--warna-utama); }
.kartu__judul {
margin-bottom: var(--ruang-2);
font-size: 1.25rem;
color: var(--warna-utama);
}
.kartu__teks { color: var(--warna-teks-redup); }
.tombol {
display: inline-block;
padding: var(--ruang-3) var(--ruang-5);
color: white;
background: var(--warna-utama);
text-decoration: none;
transition: background-color 150ms ease;
}
.tombol:hover { background: var(--warna-utama-gelap); }
}
@layer utilities {
/* Utility menang karena layer-nya paling akhir, tanpa perlu !important */
.teks-utama { color: var(--warna-utama); }
}
/* → Semua selector komponen bernilai (0,1,0) atau (0,1,1) untuk :hover.
Ganti warna merek cukup di satu baris: --warna-utama. */
<article class="kartu kartu--unggulan">
<h3 class="kartu__judul">Paket Hemat</h3>
<p class="kartu__teks">Dua gelas kopi susu aren.</p>
<a class="tombol" href="/pesan">Pesan</a>
</article>
Struktur folder yang bisa kamu pakai untuk proyek CSS murni:
css/
├── main.css # hanya berisi @layer dan @import ke file lain
├── tokens.css
├── reset.css
├── layout.css
└── components/
├── kartu.css
└── tombol.css
Catatan: @import di CSS murni membuat browser mengunduh file secara berantai, jadi untuk produksi biasanya file digabung oleh build tool seperti Vite, atau kamu memakai Sass yang menggabungkan semuanya menjadi satu file saat compile.
Kesalahan umum
- Menamai class berdasarkan tampilan (
.merah,.besar). Cara benar: namai berdasarkan peran (.tombol--bahaya,.judul-halaman). - BEM bertingkat seperti
.kartu__badan__judul. Cara benar:.kartu__judul, atau pecah jadi block baru. - Menulis selector BEM bersarang seperti
.kartu .kartu__judul. Ini menaikkan specificity tanpa manfaat. Cara benar: cukup.kartu__judul. - Memasang reset agresif lalu lupa gaya fokus dan list. Cara benar: pakai reset ringkas dan tambahkan kembali gaya penting seperti
:focus-visible. - Merapikan sambil mengubah tampilan. Refactor harus menghasilkan tampilan identik. Cara benar: bandingkan screenshot sebelum dan sesudah.
Latihan
- Mudah, Ubah HTML ini ke penamaan BEM:
<div class="profil"><img class="foto"><h2 class="nama"></h2><p class="bio"></p></div>, termasuk variasi profil "terverifikasi". - Sedang, Identifikasi minimal empat masalah pada CSS berikut, lalu tulis ulang:
#nav ul li a { color: red !important; } #nav ul li a.active { color: blue !important; font-weight: bold; } .kiri { float: left; }. - Menantang, Susun file
main.cssyang mendeklarasikan urutan layerreset, tokens, layout, components, utilities, lalu buktikan bahwa class.sembunyi { display: none; }di layerutilitiesmenang atas#app .kartu { display: block; }di layercomponents.
Lihat solusi
<!-- Soal 1 -->
<div class="profil profil--terverifikasi">
<img class="profil__foto" src="foto.jpg" alt="Foto Rina">
<h2 class="profil__nama">Rina</h2>
<p class="profil__bio">Frontend developer.</p>
</div>
/* Soal 2
Masalah: (1) styling lewat ID, (2) selector terlalu panjang dan bergantung struktur,
(3) !important dipakai untuk saling timpa, (4) nama .kiri berdasarkan tampilan,
(5) float untuk layout sudah kuno, sebaiknya Flexbox. */
.nav__link { color: red; }
.nav__link--aktif { color: blue; font-weight: bold; }
.nav__list { display: flex; gap: 1rem; }
/* Soal 3 */
@layer reset, tokens, layout, components, utilities;
@layer components {
#app .kartu { display: block; } /* specificity (1,1,0) */
}
@layer utilities {
.sembunyi { display: none; } /* specificity (0,1,0) */
}
/* → <div id="app"><div class="kartu sembunyi">...</div></div> TIDAK tampil.
Antar layer, urutan layer menang duluan sebelum specificity dibandingkan. */
Ringkasan
- Review CSS dengan mencari selector panjang,
!important, styling ID, nilai berulang, dan nama berbasis tampilan. - BEM (
block__element--modifier) menjaga specificity datar dan nama yang jelas. - Reset modern yang ringkas lebih praktis daripada reset total.
- Urutkan file dari umum ke spesifik: tokens, reset, layout, components, utilities;
@layermengunci urutan itu. - Refactor CSS harus menghasilkan tampilan identik; bandingkan sebelum dan sesudah.