Komponen adalah satu bagian UI (tombol, kolom isian, kartu kamar) yang didesain dan dikodekan sekali, dengan pilihan tampilan dan keadaan yang sudah ditentukan, lalu dipakai di mana-mana. Component library adalah kumpulan komponen itu beserta dokumentasinya. Bayangin perlengkapan kamar standar di homestay Bu Sri: tiap kamar dapat lampu baca, gantungan kunci, dan papan "Jangan diganggu" yang sama. Papannya punya dua sisi, lampunya punya dua ukuran. Kalau Bu Sri mengganti desain gantungan kunci, dia mengubah daftar perlengkapannya sekali, dan ketiga kamar ikut. Kalau tiap kamar membeli perlengkapannya sendiri-sendiri, setahun kemudian gak ada dua kamar yang sama.
🧪 Tebakan cepat: Kartu Kamar Loteng dan Kamar Kebun dulu disalin dari kartu Kamar Teras, lalu diubah sedikit. Hari ini Bu Sri menaikkan tombol di kartu Teras dari 40px ke 48px. Berapa dari tiga tombol "Pilih" yang sekarang 48px? Jawabannya ada di bagian "Satu sumber, banyak pemakaian".
Satu sumber, banyak pemakaian
Ini tiga kartu kamar yang dibuat dengan menyalin. Kami mengukur tinggi tombolnya dengan getBoundingClientRect() di Chromium, WebKit, dan Firefox, hasilnya sama:
| Kartu | Asalnya | Tinggi tombol | Yang lain |
|---|---|---|---|
| Kamar Teras | yang baru diubah Bu Sri | 48px | |
| Kamar Loteng | salinan, border: 0 |
38px | hijaunya #16803d, salah ketik |
| Kamar Kebun | salinan, padding: 8px |
42px | penuh, tapi tombolnya tetap hijau karena salinannya gak punya gaya :disabled |
Jadi jawaban tebakannya: cuma satu. Itu jawaban tebakannya. Salinan gak tahu kalau aslinya berubah, dan tiap salinan pelan-pelan menjauh. Ini disebut drift.
Komponen membalik arahnya: satu sumber, banyak pemakaian. Di JavaScript biasa, komponen bisa berupa fungsi yang menerima data dan mengembalikan markup:
<div class="rooms" id="rooms"></div>
<script>
function roomCard({ name, price, guests, status = "tersedia" }) {
const harga = "Rp " + price.toLocaleString("id-ID"); // 350000 jadi "Rp 350.000"
let aksi = `<button type="button" class="btn btn--primary btn--lg">Pilih ${name}</button>`;
if (status === "penuh") aksi = `<button type="button" class="btn btn--secondary btn--lg" disabled>Penuh 12 Des</button>`;
return `<article class="room-card room-card--${status}">
<h2>${name}</h2>
<p>${harga} / malam · ${guests} tamu</p>
${aksi}
</article>`;
}
document.querySelector("#rooms").innerHTML = [
roomCard({ name: "Kamar Teras", price: 350000, guests: 2 }),
roomCard({ name: "Kamar Loteng", price: 275000, guests: 2 }),
roomCard({ name: "Kamar Kebun", price: 425000, guests: 4, status: "penuh" }),
].join("");
</script>
roomCarddipanggil tiga kali dengan data yang berbeda. Objek{ name, price, guests, status }adalah props: masukan komponen. Di React, fungsi seperti ini memang disebut komponen, dan objeknya disebut props.status = "tersedia"adalah nilai default: kalau gak diisi, kartunya tersedia.- Mengganti
btn--lgdi satu tempat ini mengubah ketiga tombol. Diukur: 48, 48, 48px.
Anatomi: variants, states, sizes, props
Tiap komponen di library perlu empat hal: variants (versi tampilannya), states (keadaannya saat dipakai), sizes (ukurannya), dan props (masukan yang boleh diatur pemakainya). Ini ketiga komponen halaman Bu Sri:
Tombol .btn |
Kolom isian .field |
Kartu kamar .room-card |
|
|---|---|---|---|
| Variants | primary, secondary, ghost | text, tel, email | tersedia, terpilih, penuh |
| Sizes | sm 32px, md 40px, lg 48px | satu: 44px | ringkas, dengan foto |
| States | default, hover, active, focus-visible, disabled, loading | default, focus, invalid, disabled | loading (skeleton), default, foto gagal dimuat |
| Props | label, variant, size, disabled, busy | label, type, help, error, disabled | name, price, guests, status |
Di HTML dan CSS biasa, "API" komponen adalah class dan atributnya:
.btn { padding: 7px 16px; font: inherit; font-weight: 600; line-height: 24px; border: 1px solid transparent; border-radius: var(--radius-md); }
.btn--sm { padding: 3px 12px; font-size: 14px; } /* 32px */
.btn--lg { padding: 11px 16px; } /* 48px */
.btn--primary { color: var(--on-action); background: var(--action); }
.btn--secondary { color: var(--action); background: var(--surface-card); border-color: var(--action); }
.btn--ghost { color: var(--action); background: transparent; }
.btn:focus-visible { outline: 3px solid var(--text); outline-offset: 2px; }
.btn:disabled { color: var(--text-muted); background: var(--border); border-color: var(--border); cursor: not-allowed; }
.btn[aria-busy="true"] { cursor: progress; }
.btnmenyimpan semua yang sama untuk tiap tombol..btn--primarydan kawan-kawan cuma menambah yang beda: itu variants dan sizes.- Tinggi = padding atas +
line-height+ padding bawah + garis: 3 + 24 + 3 + 2 = 32, 7 + 24 + 7 + 2 = 40, 11 + 24 + 11 + 2 = 48px. - Keadaan ditulis dengan pseudo-class (
:focus-visible,:disabled) dan atribut (aria-busy), jadi pemakai komponen gak perlu ingat class tambahan. - Warnanya dari design token pelajaran sebelumnya, jadi tema gelap sampai ke tombol tanpa mengubah
.btn.
Dokumentasi komponen
Komponen tanpa dokumentasi akan dipakai salah, atau disalin lagi. Satu halaman dokumentasi berisi:
- Kegunaan: kapan dipakai, kapan jangan. "Kartu kamar untuk daftar kamar; jangan untuk ringkasan pesanan."
- Variants, sizes, dan states, masing-masing dengan contoh yang bisa dilihat.
- Tabel props, seperti ini untuk
roomCard:
| Prop | Tipe | Default | Catatan |
|---|---|---|---|
name |
string | wajib | boleh panjang: judulnya turun ke baris baru |
price |
number | wajib | per malam: 350000 ditulis "Rp 350.000" |
guests |
number | wajib | jumlah tamu maksimal |
status |
"tersedia", "terpilih", "penuh" | "tersedia" | penuh: tombol nonaktif dan tanggal kosong berikutnya |
- Catatan aksesibilitas: label tombolnya menyebut nama kamar ("Pilih Kamar Teras"), supaya tiga tombol bisa dibedakan saat dibacakan screen reader.
- Contoh kode yang bisa disalin.
Banyak tim menulisnya di Storybook, "a frontend workshop for building UI components and pages in isolation". Tiap story adalah satu keadaan komponen: tombol primary lg loading, kolom tel invalid. Lab di halaman ini bekerja dengan cara yang sama.
Pakai library atau bikin sendiri
| Jenis | Contoh | Yang kamu dapat | Yang kamu kerjakan |
|---|---|---|---|
| Library lengkap | Material UI, Ant Design | tampilan dan perilaku jadi | menyesuaikan dengan brand, sering melawan gaya bawaannya |
| Library headless | Radix Primitives, React Aria, Headless UI | perilaku: keyboard, fokus, atribut ARIA | semua CSS-nya |
| Kode yang disalin ke proyek | shadcn/ui | kode komponen masuk ke folder proyekmu lewat npx shadcn@latest add button |
merawat kode itu sendiri |
shadcn/ui menulis tentang dirinya: "This is not a component library. It is how you build your component library." Kodenya jadi milikmu.
Cara memilih untuk situs Bu Sri:
- Elemen HTML bawaan dulu. Tombol, kolom isian, dan kartu cukup HTML dan CSS sendiri:
<button>sudah bisa difokus dan ditekan dengan Enter dan Spasi, dan<dialog>denganshowModal()sudah mengurus fokus dan Esc. - Pinjam perilaku yang rumit. Kalender check-in dan check-out lain cerita: contoh date picker di WAI-ARIA Authoring Practices Guide mendokumentasikan 11 baris tombol keyboard untuk grid tanggalnya saja (panah ke empat arah, Home, End, Page Up, Shift + Page Up, dan seterusnya). Library headless memberi perilaku itu, dan tampilannya tetap milik Bu Sri.
- Library lengkap cocok untuk alat internal yang tampilannya gak harus khas, misalnya dasbor admin dengan puluhan tabel.
⚠️ Jebakan yang sering ketemu
- Menyalin, bukan memakai ulang. Salinan menjauh: 48, 38, 42px, dan hijau
#16803d. - Variant yang meledak.
.btn-hijau-besar-bulat-2untuk tiap kebutuhan baru. Tambahkan variant cuma kalau tugasnya memang beda, seperti.btn--dangeruntuk "Batalkan pesanan". - Prop yang terlalu bebas.
color="#16a34a"membuat tiap pemakai memilih warnanya sendiri, dan drift kembali lewat pintu depan. - Cuma mendesain keadaan default. Komponen belum jadi sebelum hover, focus-visible, disabled, dan loading-nya ada.
- Library untuk semua hal, lalu
!importantdi mana-mana. Kalau terus melawan gaya bawaan library lengkap, mungkin kamu butuh library headless.
TL;DR: Komponen adalah satu bagian UI yang dibuat sekali dan dipakai di mana-mana; component library adalah kumpulannya plus dokumentasi. Salinan tangan menjauh (drift: tombol 48, 38, 42px), komponen membuat satu perubahan sampai ke semua pemakai. Tiap komponen punya empat hal: variants (primary, secondary, ghost), states (default, hover, active, focus-visible, disabled, loading), sizes (sm 32, md 40, lg 48px), dan props (masukannya, misalnya name, price, guests, status). Dokumentasikan kegunaan, variants dan states, tabel props, catatan aksesibilitas, dan contoh kode, misalnya di Storybook (satu story = satu keadaan). Pakai elemen HTML bawaan dulu (<button>, <dialog>), library headless (Radix Primitives, React Aria, Headless UI) untuk perilaku rumit seperti date picker, library lengkap (Material UI, Ant Design) untuk alat internal, dan shadcn/ui kalau ingin kode komponennya jadi milikmu.