Episode ini membahas pseudo-class dan pseudo-element: state interaksi seperti hover, focus, dan focus-visible, seleksi struktural seperti nth-child dan not, serta elemen dekoratif ::before dan ::after dengan properti content.

Sejauh ini kalian memilih elemen berdasarkan nama, class, atau id. Namun banyak elemen yang ingin kita sentuh tidak bisa dibedakan dari atribut HTML-nya saja — elemen yang sedang di-hover, item urutan ganjil, atau bagian dekoratif sebelum teks. Di sinilah pseudo-class dan pseudo-element masuk. Pseudo-class menyeleksi elemen berdasarkan state atau posisi. Pseudo-element menargetkan bagian dari elemen, termasuk bagian yang tidak ada di dokumen seperti garis sebelum teks. Mengapa penting? Keduanya menghidupkan interaksi — umpan balik hover, fokus keyboard, baris bergaris-garis — dan menghindari HTML yang membengkak dengan span kosong untuk dekorasi.
State seperti :hover, :focus, dan :active memberi umpan balik pada interaksi:
.tombol {
background-color: #2563eb;
color: white;
transition: background-color 0.2s ease;
}
.tombol:hover {
background-color: #1d4ed8;
}
.tombol:focus {
outline: 2px solid #60a5fa;
outline-offset: 2px;
}
.tombol:active {
background-color: #1e40af;
}:hover merespons kursor di atas elemen, :active saat ditekan, dan :focus saat elemen menerima fokus — baik dari klik maupun Tab. Fokus adalah jembatan aksesibilitas: tanpa gaya fokus yang terlihat, pengguna keyboard kehilangan petunjuk lokasi.
.tombol:focus-visible {
outline: 2px solid #60a5fa;
outline-offset: 2px;
}:focus-visible hanya menerapkan gaya saat fokus datang dari keyboard, bukan dari klik mouse. Pola terbaik: tampilkan outline hanya untuk :focus-visible, menghindari lingkaran fokus yang mengganggu saat klik.
Pseudo-class struktural memilih elemen berdasarkan posisinya di antara saudara kandung:
li:first-child {
font-weight: bold;
}
li:last-child {
border-bottom: none;
}
tr:nth-child(even) {
background-color: #f1f5f9;
}
li:not(.disembunyikan) {
display: block;
}:first-child, :last-child, dan :nth-child memilih berdasarkan indeks. :nth-child(odd) dan :nth-child(even) membuat efek zebra pada tabel dengan mudah. :not() mengecualikan elemen yang cocok dengan selector di dalamnya.
:is(header, footer) a {
text-decoration: none;
}
:where(.kartu, .panel) {
border-radius: 8px;
}:is() dan :where() menerima daftar selector dan menghilangkan pengulangan. Keduanya berperilaku sama, kecuali spesifisitas: :is() mengambil spesifisitas tertinggi anggotanya, sedangkan :where() selalu nol — membuatnya mudah ditimpa.
::before dan ::after menyisipkan konten dekoratif di awal dan akhir elemen. Mereka hanya ada bila properti content didefinisikan:
.link-unduh::before {
content: "↓ ";
}
.link-unduh::after {
content: " (PDF)";
font-size: 0.8em;
}
.quote::before {
content: open-quote;
}
.quote::after {
content: close-quote;
}Pseudo-element ini berguna untuk ikon dekoratif, tanda kutip, label kecil, dan garis pemisah — tanpa menambah node HTML. Teks yang dihasilkan content umumnya tidak dibaca oleh pembaca layar secara konsisten, jadi jangan menaruh informasi penting di dalamnya.
.badge::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #22c55e;
margin-right: 6px;
}Untuk menggambar bentuk geometris, beri content: "" lalu styling seperti elemen biasa. Lingkaran kecil di atas menghasilkan indikator "online" tanpa markup tambahan.
.list { list-style: none; padding: 0; margin: 0; font-family: system-ui, sans-serif; }
.list li { padding: 12px 16px; border-bottom: 1px solid #e2e8f0; }
.list li:hover { background-color: #f1f5f9; }
.list li:last-child { border-bottom: none; }
.list li:nth-child(even) { background-color: #f8fafc; }
.list li:hover:nth-child(even) { background-color: #f1f5f9; }
.list a { color: #2563eb; text-decoration: none; }
.list a::after { content: " →"; }Baris genap berwarna lembut, baris pertama dan terakhir dirapikan, dan link diakhiri panah dari ::after. Semua efek ini tercapai tanpa satu pun span tambahan di HTML.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Daftar Interaktif</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<ul class="list">
<li><a href="#">Beranda</a></li>
<li><a href="#">Artikel</a></li>
<li><a href="#">Proyek</a></li>
<li><a href="#">Kontak</a></li>
</ul>
</body>
</html>Jalankan npx serve . lalu arahkan kursor ke tiap baris dan tekan Tab untuk melihat fokus. Gabungan :hover, :nth-child, dan ::after menghasilkan antarmuka yang terasa hidup dengan CSS murni.
Bila ::before atau ::after tidak terlihat, cek apakah content sudah ditentukan. Tanpa content, pseudo-element tidak dibuat sama sekali.
Teks di dalam content tidak selalu dibaca pembaca layar. Simpan informasi penting di HTML, dan gunakan pseudo-element hanya untuk dekorasi.
Jangan menghapus outline fokus tanpa pengganti. Bila menghapus default, sediakan :focus-visible yang jelas agar navigasi keyboard tetap terarah.
Inti yang harus dibawa pulang:
:hover, :focus, dan :active.:focus-visible membatasi gaya fokus untuk interaksi keyboard.:nth-child, :first-child, dan :not() memilih berdasarkan struktur.::before dan ::after menyisipkan konten dekoratif via content.:is() dan :where() mengurangi pengulangan selector.content pseudo-element.
Di episode 14 selanjutnya kita akan membahas transisi dan animasi CSS — pergerakan halus dengan transition dan urutan animasi penuh dengan @keyframes.