Kenapa ini penting
Kalau HTML adalah kerangka rumah, CSS (Cascading Style Sheets) adalah cat, lantai, perabot, dan tata ruangnya. Banyak pemula merasa CSS "tidak bisa ditebak": kenapa warnanya tidak berubah, kenapa kotaknya melebar, kenapa margin tiba-tiba hilang. Hampir semua kebingungan itu berasal dari empat konsep yang akan kamu pelajari di sini: selector, specificity, cascade, dan box model. Begitu empat hal ini kamu pahami, CSS berubah dari tebak-tebakan menjadi logika yang bisa diprediksi.
Konsep inti
Tiga cara memasang CSS
| Cara | Contoh | Kapan dipakai |
|---|---|---|
| Inline | <p style="color: red"> |
Hampir tidak pernah; sulit dirawat dan specificity-nya tinggi |
| Internal | <style> di dalam <head> |
Halaman tunggal, demo, eksperimen cepat |
| Eksternal | <link rel="stylesheet" href="style.css"> |
Standar untuk proyek nyata; satu file dipakai banyak halaman dan bisa di-cache browser |
Satu aturan CSS (rule) terdiri dari selector dan deklarasi:
/* selector { properti: nilai; } */
h1 {
color: #1f2937;
font-size: 2rem;
}
Selector
- Tipe:
p,h1,buttonmemilih semua elemen dengan nama itu. - Class:
.kartumemilih semua elemen denganclass="kartu". Ini yang paling sering kamu pakai. - ID:
#headermemilih satu elemen denganid="header". Hindari untuk styling karena specificity-nya terlalu kuat. - Universal:
*memilih semua elemen. - Atribut:
input[type="email"],a[href^="https"](nilai diawali https). - Kombinator:
- Spasi (descendant):
.kartu pmemilih semuapdi dalam.kartu, berapa pun kedalamannya. >(child):.menu > lihanya anak langsung.+(adjacent sibling):h2 + pmemilihpyang tepat setelahh2.~(general sibling):h2 ~ pmemilih semuapsaudara setelahh2.- Grup:
h1, h2, h3 { ... }memberi gaya yang sama ke beberapa selector.
Pseudo-class seperti :hover dan selector modern seperti :is(), :where(), :not(), dan :has() akan dibahas lebih dalam di modul berikutnya.
Specificity: siapa yang menang
Ketika dua aturan menargetkan elemen yang sama dengan properti yang sama, browser menghitung specificity (tingkat kekhususan). Cara mudah: anggap sebagai skor tiga angka (ID, class, tipe).
| Selector | Skor (ID, class, tipe) |
|---|---|
p |
(0, 0, 1) |
.harga |
(0, 1, 0) |
p.harga |
(0, 1, 1) |
.kartu .harga |
(0, 2, 0) |
#produk .harga |
(1, 1, 0) |
inline style="" |
mengalahkan semua selector |
Angka dibandingkan dari kiri: satu ID mengalahkan seratus class. Pseudo-class (:hover) dan atribut ([type]) dihitung seperti class. Pseudo-element (::before) dihitung seperti tipe. :where() selalu bernilai nol, sangat berguna untuk gaya dasar yang mudah ditimpa.
!important memaksa deklarasi menang di atas aturan biasa. Ini seperti membunyikan klakson terus-menerus: sekali dipakai, orang lain harus ikut membunyikan klakson lebih keras. Simpan untuk kasus darurat atau utility yang memang harus menang.
Cascade
"Cascading" berarti air terjun: aturan mengalir dan saling timpa. Urutan penentuannya, secara sederhana:
- Asal dan kepentingan: gaya bawaan browser, lalu gaya kamu, lalu gaya kamu yang
!important. - Cascade layer (
@layer), kalau dipakai. - Specificity.
- Urutan kemunculan: kalau specificity sama, aturan yang ditulis paling akhir menang.
Inheritance (pewarisan)
Beberapa properti otomatis diwariskan dari induk ke anak, terutama properti teks: color, font-family, font-size, line-height, text-align. Itu sebabnya cukup menulis font-family di body. Properti kotak seperti margin, padding, border, width, dan background tidak diwariskan. Kamu bisa memaksa pewarisan dengan nilai inherit, mengembalikan ke nilai awal dengan initial, atau memakai unset.
Box model
Setiap elemen adalah kotak berlapis, seperti paket kiriman:
- content: isi barangnya (teks, gambar).
- padding: bubble wrap di dalam kardus, jarak antara isi dan tepi.
- border: kardusnya.
- margin: jarak antar kardus di gudang.
Secara default (box-sizing: content-box), width: 300px hanya untuk content. Tambah padding: 20px dan border: 2px, lebar total jadi 344px. Ini sumber bug klasik. Solusinya, hampir semua proyek modern memasang:
*, *::before, *::after { box-sizing: border-box; }
Dengan border-box, width: 300px sudah termasuk padding dan border.
Dua hal lain tentang box model: elemen block (div, p, h1) mengambil satu baris penuh, sedangkan elemen inline (span, a, strong) mengalir di dalam teks dan mengabaikan width/height serta margin atas-bawah. Lalu ada margin collapse: margin vertikal dua blok yang berurutan tidak dijumlahkan, melainkan diambil yang terbesar.
Unit
| Unit | Relatif terhadap | Cocok untuk |
|---|---|---|
px |
Piksel CSS (tetap) | Border tipis, bayangan, detail kecil |
rem |
Font-size elemen <html> (default browser 16px) |
Ukuran font, spacing, sebagian besar ukuran |
em |
Font-size elemen itu sendiri | Padding tombol yang ikut membesar dengan teksnya |
% |
Ukuran induk | Lebar kolom fleksibel |
vw / vh |
1% lebar / tinggi viewport | Hero layar penuh, tipografi fluid |
dvh |
Tinggi viewport dinamis (memperhitungkan address bar HP) | Section setinggi layar di mobile |
ch |
Lebar karakter "0" | Membatasi panjang baris teks, misal max-width: 65ch |
Kenapa rem lebih disarankan daripada px untuk font? Kalau pengguna memperbesar ukuran huruf default di pengaturan browser (umum bagi pengguna dengan penglihatan terbatas), ukuran dalam rem ikut membesar, sedangkan px tidak.
Contoh kode
index.html:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Latihan Specificity</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="kartu" id="promo">
<h2>Paket Hemat</h2>
<p class="harga">Rp50.000</p>
<p>Berlaku sampai akhir bulan.</p>
</div>
</body>
</html>
style.css:
/* Reset box model: width sudah termasuk padding dan border */
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: system-ui, sans-serif; /* diwariskan ke semua anak */
color: #1f2937;
line-height: 1.6;
}
.kartu {
width: 320px;
padding: 1.5rem; /* 24px kalau font root 16px */
border: 2px solid #e5e7eb;
margin: 2rem auto; /* auto kiri-kanan = rata tengah */
}
/* → Lebar total tetap 320px berkat border-box (tanpa itu: 320 + 48 + 4 = 372px) */
p { color: gray; } /* (0,0,1) */
.harga { color: green; } /* (0,1,0) menang atas p */
.kartu .harga { color: orange; } /* (0,2,0) menang atas .harga */
#promo .harga { color: crimson; } /* (1,1,0) menang atas semuanya */
/* → Teks "Rp50.000" berwarna crimson */
.kartu p { color: gray; }
p { color: blue; } /* ditulis belakangan, tapi specificity lebih rendah */
/* → "Berlaku sampai..." tetap gray, karena (0,1,1) > (0,0,1); urutan hanya menentukan kalau skornya sama */
h2 {
font-size: 1.5rem;
margin-bottom: 0.5em; /* 0.5 × font-size h2 = 12px */
}
Tips: klik kanan elemen, pilih Inspect. Di panel Styles, aturan yang kalah akan dicoret. Panel Computed menunjukkan nilai akhir dan diagram box model.
Kesalahan umum
- Memakai ID untuk styling, lalu kesulitan menimpanya. Cara benar: styling dengan class, ID untuk anchor link dan JavaScript.
- Menambal dengan
!importantsetiap kali gaya tidak berubah. Cara benar: cek di DevTools aturan mana yang menang, lalu perbaiki specificity atau urutan. - Lupa
box-sizing: border-box, sehingga elemen dengan padding meluber dari kontainernya. Cara benar: pasang reset di awal file. - Bingung
embertumpuk:font-size: 1.2emdi dalam induk yang juga1.2emmenghasilkan 1.44 kali. Cara benar: pakairemuntuk font-size,emhanya untuk spacing yang memang harus mengikuti teks. - Memberi
widthatau margin atas ke elemen inline lalu heran tidak berubah. Cara benar: ubah kedisplay: inline-blockataublock.
Latihan
- Mudah, Hitung specificity:
nav ul li a,.menu a:hover,#app .menu > a. - Sedang, Buat kotak
.kotakdengan lebar 400px, padding 2rem, border 4px. Tanpaborder-box, berapa lebar totalnya? Lalu buat versi yang lebar totalnya tepat 400px. - Menantang, Kamu punya
.tombol { background: blue; }di file framework dan tidak boleh mengubah file itu. Elemen kamu<a class="tombol tombol-bahaya">. Tulis CSS agar tombol berwarna merah tanpa!importantdan tanpa ID, lalu jelaskan kenapa berhasil.
Lihat solusi
/* Soal 1
nav ul li a → (0,0,4)
.menu a:hover → (0,2,1) karena :hover dihitung seperti class
#app .menu > a → (1,1,1) kombinator > tidak menambah skor
*/
/* Soal 2
Tanpa border-box: 400 + (32 × 2) + (4 × 2) = 472px
*/
.kotak {
box-sizing: border-box;
width: 400px;
padding: 2rem;
border: 4px solid #333;
}
/* → total tepat 400px, area konten jadi 400 - 64 - 8 = 328px */
/* Soal 3: dua cara */
/* Cara A: specificity lebih tinggi (0,2,0) > (0,1,0) */
.tombol.tombol-bahaya { background: red; }
/* Cara B: specificity sama (0,1,0), tetapi file kamu dimuat SETELAH file framework,
sehingga menang karena urutan */
.tombol-bahaya { background: red; }
Ringkasan
- Pasang CSS lewat file eksternal; hindari inline style.
- Specificity dihitung (ID, class, tipe); satu ID mengalahkan banyak class. Kalau seri, aturan terakhir menang.
- Properti teks diwariskan, properti kotak tidak.
- Box model: content, padding, border, margin. Pakai
box-sizing: border-box. - Pakai
remuntuk font dan spacing,%atauvwuntuk ukuran fleksibel,pxuntuk detail kecil.