Episode ini membahas integrasi CSS dengan HTML dan JavaScript modern: CSS Modules yang membatasi cakupan class, CSS-in-JS yang menulis style di komponen, manipulasi class dan custom properties dari JavaScript, serta fitur modern seperti container queries dan selector :has.

Episode 20 membahas arsitektur CSS di dalam file stylesheet. Episode 21 menggeser fokus ke integrasi: bagaimana CSS hidup berdampingan dengan JavaScript dan framework modern yang merender HTML secara dinamis.
Ekosistem modern menawarkan beberapa model: CSS Modules yang mengisolasi class per komponen, CSS-in-JS yang membawa style ke dalam kode komponen, serta API browser seperti classList dan custom properties yang menjembatani CSS dan JS.
Mengapa penting? Hampir semua aplikasi web kini dibangun dengan komponen. Cara CSS diorganisasi dan dipanggil menentukan apakah komponen bisa dipakai ulang tanpa tabrakan nama dan apakah state UI bisa diubah dengan mulus.
CSS Modules mengubah class menjadi unik per file saat build, mencegah tabrakan nama antar komponen:
.kartu { border: 1px solid #e2e8f0; border-radius: 12px; padding: 24px; }
.tombol { background-color: #2563eb; color: white; padding: 10px 18px; border-radius: 8px; border: none; }import styles from "./Card.module.css";
export default function Card() {
return (
<div className={styles.kartu}>
<h2>Kartu Modul</h2>
<button className={styles.tombol}>Aksi</button>
</div>
);
}Class kartu dan tombol diubah menjadi nama unik seperti _kartu_1f2a3b. Dua komponen bisa memakai nama class yang sama tanpa bentrok, karena hasil build selalu berbeda. Struktur ini bekerja di framework seperti Next.js dan Vite tanpa konfigurasi tambahan.
CSS-in-JS menulis style langsung di dalam komponen, biasanya dengan tagged template:
import styled from "styled-components";
const Tombol = styled.button`
background-color: #2563eb;
color: white;
padding: 10px 18px;
border-radius: 8px;
border: none;
&:hover {
background-color: #1d4ed8;
}
`;
export default function Aksi() {
return <Tombol>Kirim</Tombol>;
}Style berada satu tempat dengan markup, bisa menerima props, dan scoped ke komponen. Kelemahannya: CSS dirender di runtime dan menambah ukuran bundle JavaScript. Pustaka lain seperti Emotion dan vanilla-extract menawarkan trade-off berbeda — vanilla-extract bahkan menghasilkan file CSS statis saat build.
Untuk state yang berubah, JavaScript mengontrol class lewat classList:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Class Toggle</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<button id="menu" class="menu">Menu</button>
<button id="toggle">Buka</button>
</body>
</html>.menu { transition: transform 0.3s ease; }
.menu.terbuka { transform: translateX(24px); background-color: #2563eb; color: white; }const menu = document.getElementById("menu");
const toggle = document.getElementById("toggle");
toggle.addEventListener("click", () => {
menu.classList.toggle("terbuka");
});classList.add, remove, dan toggle mengganti state CSS dengan aman — tanpa menulis inline style. Menggabungkan class dengan aturan CSS membuat state terpusat di satu tempat dan mudah dianimasikan.
Custom properties bisa dibaca dan diubah dari JavaScript sebagai jembatan nilai dinamis:
<div class="bar" id="bar">Memuat</div>
<button id="atur">Atur 75%</button>.bar { width: var(--progres, 0%); background-color: #2563eb; color: white; padding: 8px; transition: width 0.3s ease; }const bar = document.getElementById("bar");
const atur = document.getElementById("atur");
atur.addEventListener("click", () => {
bar.style.setProperty("--progres", "75%");
});style.setProperty menulis custom property langsung ke elemen, dan CSS merender hasilnya. Pola ini umum untuk progress bar, posisi scroll, dan nilai yang datang dari data runtime — CSS menangani tampilan, JavaScript hanya menyediakan angka.
Dua fitur modern mengubah cara integrasi komponen:
.kartu { container-type: inline-size; }
@container (min-width: 400px) { .kartu { display: flex; gap: 16px; } }Container queries menyesuaikan gaya berdasarkan ukuran container, bukan viewport. Komponen yang sama bisa tampil berbeda di sidebar sempit dan area konten lebar — batas responsivitas kini pindah ke komponen itu sendiri.
.form:has(input:checked) { border-color: #16a34a; }
.kartu:has(.tag-spesial) { background-color: #fef3c7; }Selector :has() memilih elemen yang mengandung sesuatu. Kode di atas memberi border hijau pada form dengan checkbox tercentang dan menandai kartu yang punya tag spesial — seluruhnya tanpa JavaScript.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Integrasi CSS</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<main class="panel">
<div class="kartu" id="kartu">
<h2>Kartu Dinamis</h2>
<p>Status berubah lewat class dan custom property.</p>
</div>
<button id="toggle" class="aksi">Tandai Aktif</button>
</main>
</body>
</html>* { box-sizing: border-box; }
body { margin: 0; padding: 24px; font-family: system-ui, sans-serif; background-color: #f1f5f9; }
.panel { max-width: 480px; margin-inline: auto; padding: 24px; background-color: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(15, 23, 42, 0.1); }
.kartu { padding: 24px; border: 1px solid #e2e8f0; border-radius: 12px; transition: border-color 0.2s ease, background-color 0.2s ease; }
.kartu.aktif { border-color: #16a34a; background-color: #f0fdf4; }
.aksi { margin-top: 16px; padding: 10px 18px; border: none; border-radius: 8px; background-color: #2563eb; color: white; font-size: 1rem; cursor: pointer; }
.panel:has(.kartu.aktif) .aksi { background-color: #16a34a; }const kartu = document.getElementById("kartu");
const toggle = document.getElementById("toggle");
toggle.addEventListener("click", () => {
kartu.classList.toggle("aktif");
const aktif = kartu.classList.contains("aktif");
toggle.textContent = aktif ? "Nonaktifkan" : "Tandai Aktif";
});Jalankan npx serve . dan klik tombol. classList menukar state kartu, sementara :has() membuat tombol mengubah warna tanpa satu baris kondisional. CSS dan JS bekerja bersama dengan tanggung jawab yang jelas.
Menulis element.style untuk banyak properti menyulitkan maintenance. Gunakan class untuk state dan custom properties untuk nilai dinamis.
Bila class ditambah-tambahkan tanpa aturan, HTML sulit dibaca. Tetapkan konvensi — misalnya state selalu diawali is- — agar maksud class jelas.
container-type: inline-size pada elemen tanpa kebutuhan responsif membuang siklus layout. Terapkan hanya pada komponen yang memang perlu menyesuaikan diri.
Inti yang harus dibawa pulang:
classList menukar state UI dengan aman dan terpusat.style.setProperty.:has() memilih elemen berdasarkan isinya tanpa JavaScript.
Di episode 22 selanjutnya kita akan membahas performance CSS dan rendering optimal — mempercepat rendering, menghindari layout shift, dan menyiapkan stylesheet untuk produksi.