Kenapa ini penting
Komponen yang benar secara logika tapi tampil berantakan tetap terasa "rusak" di mata pengguna. Di React ada beberapa cara memberi style, dan masing-masing punya tempatnya. Kalau kamu tidak paham bedanya, biasanya terjadi dua masalah: style bocor ke komponen lain (tombol di halaman A ikut berubah karena class .button di halaman B), atau kode JSX penuh objek style panjang yang sulit dibaca. Modul ini membahas empat cara paling dasar: style object (inline style), file CSS biasa, CSS Modules, dan conditional className. Keempatnya bawaan Vite, tanpa library tambahan.
Konsep inti
1. Style object (atribut style)
Di HTML kamu menulis style="color: red; font-size: 16px". Di JSX, atribut style tidak menerima string, melainkan object JavaScript. Nama property ditulis dengan camelCase (fontSize, bukan font-size), dan nilainya berupa string atau angka. Angka tanpa satuan otomatis dianggap px untuk property yang butuh satuan (misalnya padding: 8 menjadi 8px).
Karena ini object JavaScript, kamu bebas menghitung nilainya dari state atau props, misalnya lebar progress bar width: persen + '%'. Inilah kekuatan utama inline style: nilai yang dinamis per-render. Kelemahannya: tidak bisa memakai :hover, @media, atau pseudo-element ::before, dan kalau objeknya besar, JSX jadi penuh.
Analogi: inline style itu seperti menempel sticky note langsung di satu barang. Cepat, tapi kalau kamu punya 50 barang yang mau ditandai sama, lebih baik pakai label resmi (class CSS).
2. File CSS biasa
Vite mengizinkan kamu import './Card.css' langsung di file komponen. CSS-nya akan dimasukkan ke halaman. Atribut class di HTML ditulis className di JSX karena class adalah kata kunci di JavaScript.
Hal penting yang sering tidak disadari pemula: CSS biasa itu global. Walaupun kamu import Card.css di dalam Card.jsx, aturan .title di dalamnya berlaku untuk semua elemen bernama class title di seluruh aplikasi. Komponen hanya "membawa" file itu, bukan "mengurungnya".
3. CSS Modules
CSS Modules menyelesaikan masalah bocor tadi. Cukup beri nama file berakhiran .module.css, misalnya Card.module.css. Saat di-import, kamu mendapat object yang memetakan nama class asli ke nama class unik buatan Vite (misalnya title menjadi _title_1x2ab_3). Jadi .title di Card.module.css tidak akan pernah bentrok dengan .title di file lain.
| Cara | Scope | Bisa :hover / @media | Cocok untuk |
|---|---|---|---|
| Style object | Satu elemen | Tidak | Nilai dinamis (lebar, warna dari data) |
| CSS biasa | Global | Ya | Reset, font, variabel global |
| CSS Modules | Per file (lokal) | Ya | Style komponen sehari-hari |
| Utility class (Tailwind) | Per elemen lewat class | Ya | Dibahas di modul berikutnya |
4. Conditional className
Sering kali tampilan berubah tergantung state: tombol aktif, input error, item selesai dicoret. Caranya: susun string className berdasarkan kondisi. Bisa memakai template literal, operator ternary, atau trik array + filter(Boolean).join(' ') supaya tidak ada spasi atau kata false nyasar. Banyak proyek memakai library kecil clsx untuk ini, tapi kamu perlu paham dulu versi manualnya.
Memilih cara yang tepat: alur keputusan
Saat akan memberi style, tanyakan tiga hal berurutan. Pertama, apakah style ini berlaku untuk seluruh aplikasi (font dasar, warna latar body, reset margin)? Kalau ya, taruh di index.css yang di-import sekali di main.jsx. Kedua, apakah nilainya berubah-ubah mengikuti data saat aplikasi berjalan, misalnya lebar bar sesuai persentase, warna label sesuai kategori dari server, atau posisi elemen yang diseret? Kalau ya, pakai style object untuk property yang dinamis itu saja. Ketiga, selain dua kasus tadi, tulis di CSS Modules milik komponen tersebut.
Ada juga trik yang menggabungkan dua dunia: CSS custom property (variabel CSS). Kamu bisa mengirim nilai dinamis lewat style object, misalnya style={{ '--warna': warna }}, lalu memakainya di CSS Modules dengan background: var(--warna). Dengan cara ini :hover dan media query tetap bisa dipakai, sementara nilainya tetap datang dari props.
Soal penamaan di CSS Modules: karena nama class otomatis dibuat unik, kamu boleh memakai nama pendek dan jelas seperti .item, .judul, .aktif. Tidak perlu awalan panjang ala BEM (.task-item__title--active). Jika nama class memakai tanda minus, aksesnya dengan kurung siku: styles['judul-besar']. Agar lebih nyaman, banyak tim memilih camelCase (.judulBesar) sehingga bisa ditulis styles.judulBesar.
Contoh kode
Pertama, style object yang dinamis: progress bar.
// ProgressBar.jsx
export default function ProgressBar({ persen }) {
// object style dihitung ulang tiap render, jadi ikut perubahan props
const wadah = {
background: '#e5e7eb',
borderRadius: 8, // angka → 8px
height: 12,
overflow: 'hidden',
};
const isi = {
width: `${persen}%`,
height: '100%',
background: persen >= 100 ? '#16a34a' : '#2563eb', // hijau kalau sudah penuh
transition: 'width 0.3s',
};
return (
<div style={wadah} role="progressbar" aria-valuenow={persen}>
<div style={isi} />
</div>
);
}
// <ProgressBar persen={40} /> // → bar biru terisi 40%
// <ProgressBar persen={100} /> // → bar hijau penuh
Kedua, CSS Modules plus conditional className untuk kartu tugas.
/* TaskItem.module.css */
.item {
display: flex;
gap: 8px;
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 8px;
}
.item:hover {
background: #f9fafb;
}
.selesai {
opacity: 0.6;
text-decoration: line-through;
}
.penting {
border-color: #dc2626;
}
// TaskItem.jsx
import styles from './TaskItem.module.css';
export default function TaskItem({ judul, selesai, penting, onToggle }) {
// susun class: buang yang bernilai false, lalu gabung dengan spasi
const kelas = [
styles.item,
selesai && styles.selesai,
penting && styles.penting,
]
.filter(Boolean)
.join(' ');
return (
<label className={kelas}>
<input type="checkbox" checked={selesai} onChange={onToggle} />
{judul}
</label>
);
}
// Kalau selesai=true dan penting=false, class yang muncul di DOM kira-kira:
// → "_item_k3j1a_1 _selesai_k3j1a_12"
Ketiga, file CSS global untuk hal-hal yang memang global.
// main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css'; // reset & font global, sengaja berlaku ke semua
import App from './App.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>
);
Kesalahan umum
- Menulis
style="color: red"sebagai string. React akan error karenastylewajib object. Benar:style={{ color: 'red' }}. Kurung kurawal pertama berarti "ekspresi JavaScript", kurung kedua adalah object-nya. - Memakai
font-sizeataubackground-colordi style object. Tanda minus tidak valid sebagai nama property tanpa kutip. Pakai camelCase:fontSize,backgroundColor. - Mengira CSS biasa otomatis lokal.
import './Card.css'tetap global. Kalau ingin aman dari bentrok, ganti jadiCard.module.cssdan pakaistyles.namaClass. - Menghasilkan class
"false"atau"undefined". ContohclassName={'btn ' + (aktif && 'aktif')}akan menghasilkan"btn false"saataktifbernilai false. Pakai ternaryaktif ? 'aktif' : ''atau polafilter(Boolean). - Menaruh semua style di inline object. Kamu kehilangan
:hover, media query, dan JSX jadi sulit dibaca. Simpan inline style untuk nilai yang benar-benar dinamis saja.
Latihan
- Mudah, Buat komponen
Badgeyang menerima propswarnadanteks, lalu menampilkan teks dengan background sesuaiwarnamemakai style object. - Sedang, Buat komponen
Tomboldengan CSS Modules yang punya varianprimarydandanger(propsvarian) serta statedisabled. Varian dan disabled harus mengubah class. - Menantang, Buat komponen
Avataryang ukurannya (propsukurandalam px) diatur lewat style object, tapi efek hover (bayangan) dan bentuk bulat diatur lewat CSS Modules. Jelaskan dalam komentar kenapa pembagiannya begitu.
Lihat solusi
// 1. Badge.jsx
export function Badge({ warna = '#2563eb', teks }) {
return (
<span style={{ background: warna, color: 'white', padding: '2px 8px', borderRadius: 999 }}>
{teks}
</span>
);
}
// <Badge warna="#16a34a" teks="Lunas" /> → label hijau bertuliskan Lunas
/* 2. Tombol.module.css */
.btn { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; color: white; }
.primary { background: #2563eb; }
.danger { background: #dc2626; }
.disabled { opacity: 0.5; cursor: not-allowed; }
// 2. Tombol.jsx
import styles from './Tombol.module.css';
export function Tombol({ varian = 'primary', disabled = false, children, ...rest }) {
const kelas = [styles.btn, styles[varian], disabled && styles.disabled]
.filter(Boolean)
.join(' ');
return (
<button className={kelas} disabled={disabled} {...rest}>
{children}
</button>
);
}
/* 3. Avatar.module.css */
.avatar { border-radius: 50%; object-fit: cover; transition: box-shadow 0.2s; }
.avatar:hover { box-shadow: 0 4px 12px rgb(0 0 0 / 0.3); }
// 3. Avatar.jsx
import styles from './Avatar.module.css';
export function Avatar({ src, ukuran = 48, alt }) {
// ukuran berasal dari props (dinamis) → style object
// hover & bentuk bulat statis dan butuh :hover → CSS Modules
return (
<img className={styles.avatar} src={src} alt={alt} style={{ width: ukuran, height: ukuran }} />
);
}
Ringkasan
- Atribut
styledi JSX menerima object dengan nama property camelCase; cocok untuk nilai yang dinamis. - File
.cssbiasa yang di-import tetap berlaku global ke seluruh aplikasi. - File
.module.cssmenghasilkan nama class unik sehingga style tidak bocor antar komponen. - Conditional className disusun dari kondisi; hindari class
falsedengan ternary ataufilter(Boolean). - Pilih cara sesuai kebutuhan: global untuk reset, Modules untuk komponen, inline untuk nilai dinamis.