Dengan token di tempat, Bagian 2 membangun komponen interaktif pertama library: tombol dan input formulir. Keduanya dipilih karena menyumbang porsi terbesar keputusan visual di hampir semua aplikasi, sekaligus paling rawan ditulis asal — satu tombol satu padding, input satu warna border. Kita menyelesaikannya dengan pola yang sama seperti badge: kelas dasar memegang geometri, kelas varian dan kelas ukuran hanya menimpa warna serta dimensi yang memang berbeda.

Tujuan Bagian dan Prasyarat #

Target bagian ini pada styles/komponen.css:

  • .btn dengan tiga varian warna: .btn-primer, .btn-kedua, dan .btn-hantu.
  • Dua kelas ukuran: .btn-kecil dan .btn-besar, ditambah status :disabled.
  • Komponen formulir: .field, .label, .input, dan .hint.
  • Status input lewat kelas: .input-sukses, .input-galat, .hint-galat, serta :disabled.

Konsepnya bersumber dari Styling Buttons , Styling Inputs , dan Component States ; perilaku :disabled diulas pada Checked dan Disabled , sedangkan status validasi pada Validation States . Bagian ini memakai pola kelas untuk status agar mudah diuji otomatis.

Keluarga Tombol #

Tulis blok berikut setelah Bagian 1. Kelas dasar .btn memegang seluruh geometri; border transparan setebal 1px menjaga tinggi tombol primer sama dengan tombol kedua yang berborder nyata.

css styles/komponen.css — Bagian 2 (tombol)
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  column-gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  font: inherit;
  font-size: var(--text-base);
  font-weight: 500;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  text-decoration: none;
  transition: background-color 150ms ease, border-color 150ms ease,
    color 150ms ease;
}

.btn-primer {
  background-color: var(--color-primary);
  color: var(--color-on-primary);
}

.btn-primer:hover {
  background-color: var(--color-primary-hover);
}

.btn-kedua {
  background-color: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-border);
}

.btn-hantu {
  background-color: transparent;
  color: var(--color-primary);
}

.btn-kecil {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
}

.btn-besar {
  padding: var(--space-3) var(--space-6);
  font-size: var(--text-lg);
}

.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

Markupnya merangkai seluruh varian dan ukuran dalam satu baris galeri:

html index.html — galeri tombol
<button class="btn btn-primer">Simpan</button>
<button class="btn btn-kedua">Pratinjau</button>
<button class="btn btn-hantu">Batal</button>
<button class="btn btn-primer btn-besar">Simpan Besar</button>
<button class="btn btn-primer btn-kecil">Simpan Kecil</button>
<button class="btn btn-primer" disabled>Arsip</button>

Hasil di Browser:

text
Tombol primer berlatar rgb(13, 148, 136) dengan teks putih,
padding 8px 16px, dan radius 10px. Varian besar menaikkan teks
ke 20px dengan padding 12px 24px; varian kecil menurunkan teks
ke 14px. Tombol nonaktif memiliki opacity 0.55 dan cursor
not-allowed. Tombol kedua berlatar rgb(255, 255, 255) dengan
border rgb(228, 228, 231).

Input dan Status Formulir #

Komponen formulir memakai wadah .field berbasis grid agar label, input, dan hint otomatis berjarak 8px tanpa margin manual:

css styles/komponen.css — Bagian 2 (formulir)
.field {
  display: grid;
  row-gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.label {
  font-size: var(--text-sm);
  font-weight: 500;
}

.input {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font: inherit;
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.input:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-color: var(--color-primary);
}

.input:disabled {
  background-color: var(--color-bg);
  color: var(--color-muted);
  cursor: not-allowed;
}

.input-sukses {
  border-color: var(--color-success);
}

.input-galat {
  border-color: var(--color-danger);
}

.hint {
  font-size: var(--text-xs);
  color: var(--color-muted);
}

.hint-galat {
  color: var(--color-danger);
}
html index.html — field dengan status galat
<div class="field">
  <label class="label" for="email">Email</label>
  <input class="input input-galat" id="email" type="email" value="budi#example.com">
  <p class="hint hint-galat">Format email belum benar.</p>
</div>

Bedah Keluarga Komponen #

  1. font: inherit pada .btn dan .input: elemen button dan input tidak mewarisi font dari induk secara bawaan. Tanpa baris ini, teks tombol tetap memakai font sistem dan ukuran bawaan browser meski body sudah diatur.
  2. Urutan varian sebelum ukuran: .btn-primer dan .btn-kecil specificity-nya sama, sehingga urutan penulisan yang menentukan. Kelas ukuran ditulis terakhir karena menimpa padding dan font-size dari kelas dasar — mekanisme cascade yang dibahas di Avoiding Specificity Conflicts .
  3. :disabled, bukan kelas .disabled: pseudo-class ini hanya berlaku pada kontrol yang benar-benar dinonaktifkan lewat atribut disabled, sehingga gaya tidak mungkin meleset dari perilaku aksesibilitasnya.
  4. .field berbasis grid: row-gap: 8px mengatur jarak antar baris label-input-hint sekaligus; margin-bottom: 16px memisahkan antar field. Dua jarak, dua properti, tanpa angka literal.
  5. Status sebagai pasangan kelas: .input-galat selalu berpasangan dengan .hint-galat agar border dan pesan berbicara warna yang sama (--color-danger). Versi pseudo-class :invalid dibahas tuntas di chapter Validation States.

Uji Bagian Ini #

Halaman uji bagian ini tersedia di /tests/css/proyek/component-library/buttons-dan-inputs.html , dengan spesifikasi lengkap pada tests/buttons-dan-inputs.spec.js:

ElemenPropertiDiharapkan
.btn-primerbackground-colorrgb(13, 148, 136)
.btn-primercolorrgb(255, 255, 255)
.btn-primerpadding-top8px
.btn-primerborder-radius10px
.btn-besarfont-size20px
.btn-besarpadding-top12px
.btn-keduaborder-top-colorrgb(228, 228, 231)
.btn (disabled)opacity0.55
.btn (disabled)cursornot-allowed
.inputborder-top-colorrgb(228, 228, 231)
.inputborder-radius6px
.inputfont-size16px
.input-galatborder-top-colorrgb(220, 38, 38)
.hintfont-size12px
.fieldrow-gap8px

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

styles/komponen.css

Unduh berkas
css
/* =========================================================
   Proyek: Component Library (Rakit UI) — styles/komponen.css
   File utuh library yang berkembang mengikuti empat bagian:
   Bagian 1 (Design Tokens), Bagian 2 (Buttons dan Inputs),
   Bagian 3 (Cards dan Navigation), Bagian 4 (Themes dan
   Documentation). Komentar "Bagian N" menandai kelompok
   aturan yang dibahas pada setiap bagian.
   ========================================================= */

/* Bagian 1: Design Tokens --------------------------------- */

:root {
  /* Token primitif: skala netral zinc */
  --zinc-50: #fafafa;
  --zinc-100: #f4f4f5;
  --zinc-200: #e4e4e7;
  --zinc-400: #a1a1aa;
  --zinc-500: #71717a;
  --zinc-600: #52525b;
  --zinc-700: #3f3f46;
  --zinc-800: #27272a;
  --zinc-900: #18181b;

  /* Token primitif: skala teal untuk warna primer */
  --teal-500: #14b8a6;
  --teal-600: #0d9488;
  --teal-700: #0f766e;

  /* Token primitif: warna status */
  --hijau-600: #16a34a;
  --merah-600: #dc2626;

  /* Token semantik: peran, bukan warna */
  --color-bg: #fafafa;
  --color-surface: #ffffff;
  --color-text: #18181b;
  --color-muted: #52525b;
  --color-border: #e4e4e7;
  --color-primary: #0d9488;
  --color-primary-hover: #0f766e;
  --color-on-primary: #ffffff;
  --color-primary-soft: rgb(13 148 136 / 10%);
  --color-success: #16a34a;
  --color-danger: #dc2626;

  /* Skala spasi (kelipatan 4px) */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;

  /* Radius */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-full: 999px;

  /* Bayangan */
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 6%);
  --shadow-md: 0 4px 12px rgb(0 0 0 / 10%);

  /* Tipografi */
  --font-sans: system-ui, sans-serif;
  --font-mono: ui-monospace, monospace;
  --text-xs: 12px;
  --text-sm: 14px;
  --text-base: 16px;
  --text-lg: 20px;
  --text-xl: 24px;
}

* {
  margin: 0;
  box-sizing: border-box;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--color-text);
  background-color: var(--color-bg);
}

.wadah {
  max-width: 960px;
  margin: 0 auto;
  padding: var(--space-6);
}

.badge {
  display: inline-flex;
  align-items: center;
  column-gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-full);
  background-color: var(--color-primary-soft);
  color: var(--color-primary);
  font-size: var(--text-sm);
  font-weight: 500;
}

/* Bagian 2: Buttons dan Inputs ---------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  column-gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  font: inherit;
  font-size: var(--text-base);
  font-weight: 500;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  text-decoration: none;
  transition: background-color 150ms ease, border-color 150ms ease,
    color 150ms ease;
}

.btn-primer {
  background-color: var(--color-primary);
  color: var(--color-on-primary);
}

.btn-primer:hover {
  background-color: var(--color-primary-hover);
}

.btn-kedua {
  background-color: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-border);
}

.btn-hantu {
  background-color: transparent;
  color: var(--color-primary);
}

.btn-kecil {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
}

.btn-besar {
  padding: var(--space-3) var(--space-6);
  font-size: var(--text-lg);
}

.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.field {
  display: grid;
  row-gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.label {
  font-size: var(--text-sm);
  font-weight: 500;
}

.input {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font: inherit;
  color: var(--color-text);
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.input:focus {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  border-color: var(--color-primary);
}

.input:disabled {
  background-color: var(--color-bg);
  color: var(--color-muted);
  cursor: not-allowed;
}

.input-sukses {
  border-color: var(--color-success);
}

.input-galat {
  border-color: var(--color-danger);
}

.hint {
  font-size: var(--text-xs);
  color: var(--color-muted);
}

.hint-galat {
  color: var(--color-danger);
}

/* Bagian 3: Cards dan Navigation -------------------------- */

.navbar {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  column-gap: var(--space-4);
  padding: var(--space-3) var(--space-6);
  background-color: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}

.nav-brand {
  display: inline-flex;
  align-items: center;
  column-gap: var(--space-2);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--color-text);
  text-decoration: none;
}

.nav-menu {
  display: flex;
  column-gap: var(--space-6);
  list-style: none;
}

.nav-tautan {
  color: var(--color-muted);
  font-weight: 500;
  text-decoration: none;
}

.nav-tautan:hover {
  color: var(--color-primary);
}

.tabs {
  display: flex;
  column-gap: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}

.tab {
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-muted);
  background-color: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  cursor: pointer;
}

.tab-aktif {
  color: var(--color-primary);
  border-bottom-color: var(--color-primary);
}

.grid-kartu {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
  margin-top: var(--space-6);
}

.card {
  display: flex;
  flex-direction: column;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.card-shadow {
  box-shadow: var(--shadow-md);
}

.card-badan {
  display: grid;
  row-gap: var(--space-2);
  padding: var(--space-6);
}

.card-judul {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--color-text);
}

.card-teks {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-muted);
}

.card-aksi {
  display: flex;
  column-gap: var(--space-2);
  margin-top: var(--space-2);
}

/* Bagian 4: Themes dan Documentation ---------------------- */

[data-theme="gelap"] {
  --color-bg: #18181b;
  --color-surface: #27272a;
  --color-text: #fafafa;
  --color-muted: #a1a1aa;
  --color-border: #3f3f46;
  --color-primary-soft: rgb(20 184 166 / 16%);
  --shadow-md: 0 4px 12px rgb(0 0 0 / 40%);
}

.tema-sampel {
  padding: var(--space-6);
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}

.docs-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-4);
}

.swatch {
  display: block;
  width: 64px;
  height: 40px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.swatch-nama {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-muted);
}

.contoh-kode {
  padding: var(--space-3) var(--space-4);
  background-color: var(--zinc-900);
  color: var(--zinc-200);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  border-radius: var(--radius-md);
  overflow-x: auto;
}

tests/buttons-dan-inputs.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Component Library
// Bagian 2: Buttons dan Inputs
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/component-library/buttons-dan-inputs.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // .btn-primer: geometri dari kelas dasar, warna dari token semantik.
  { elemen: ".btn-primer", properti: "background-color", diharapkan: "rgb(13, 148, 136)" },
  { elemen: ".btn-primer", properti: "color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".btn-primer", properti: "padding-top", diharapkan: "8px" },
  { elemen: ".btn-primer", properti: "padding-left", diharapkan: "16px" },
  { elemen: ".btn-primer", properti: "border-radius", diharapkan: "10px" },
  { elemen: ".btn-primer", properti: "font-weight", diharapkan: "500" },

  // .btn-besar: ukuran menimpa padding dan font-size kelas dasar.
  { elemen: ".btn-besar", properti: "font-size", diharapkan: "20px" },
  { elemen: ".btn-besar", properti: "padding-top", diharapkan: "12px" },

  // .btn-kedua: permukaan putih dengan border dari token garis.
  { elemen: ".btn-kedua", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".btn-kedua", properti: "color", diharapkan: "rgb(24, 24, 27)" },
  { elemen: ".btn-kedua", properti: "border-top-color", diharapkan: "rgb(228, 228, 231)" },

  // .btn:disabled: pseudo-class pada tombol dengan atribut disabled.
  { elemen: "button.btn[disabled]", properti: "opacity", diharapkan: "0.55" },
  { elemen: "button.btn[disabled]", properti: "cursor", diharapkan: "not-allowed" },

  // .input: border, radius, dan tipografi dari token.
  { elemen: ".input", properti: "border-top-color", diharapkan: "rgb(228, 228, 231)" },
  { elemen: ".input", properti: "border-radius", diharapkan: "6px" },
  { elemen: ".input", properti: "font-size", diharapkan: "16px" },
  { elemen: ".input", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },

  // .input-galat: border berganti ke warna bahaya.
  { elemen: ".input-galat", properti: "border-top-color", diharapkan: "rgb(220, 38, 38)" },

  // .hint: teks bantu kecil dan redup.
  { elemen: ".hint", properti: "font-size", diharapkan: "12px" },
  { elemen: ".hint", properti: "color", diharapkan: "rgb(82, 82, 91)" },

  // .field: grid dengan jarak baris 8px dan pemisah antar field 16px.
  { elemen: ".field", properti: "row-gap", diharapkan: "8px" },
  { elemen: ".field", properti: "margin-bottom", diharapkan: "16px" },
];

tests/cards-dan-navigation.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Component Library
// Bagian 3: Cards dan Navigation
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/component-library/cards-dan-navigation.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // .navbar: menempel di atas halaman dengan z-index tinggi.
  { elemen: ".navbar", properti: "position", diharapkan: "sticky" },
  { elemen: ".navbar", properti: "top", diharapkan: "0px" },
  { elemen: ".navbar", properti: "z-index", diharapkan: "100" },
  { elemen: ".navbar", properti: "padding-top", diharapkan: "12px" },
  { elemen: ".navbar", properti: "column-gap", diharapkan: "16px" },

  // .nav-menu: flex dengan jarak antar tautan dari token spasi.
  { elemen: ".nav-menu", properti: "display", diharapkan: "flex" },
  { elemen: ".nav-menu", properti: "column-gap", diharapkan: "24px" },

  // .card: permukaan putih, border token, radius dari token bentuk.
  { elemen: ".card", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".card", properti: "border-top-color", diharapkan: "rgb(228, 228, 231)" },
  { elemen: ".card", properti: "border-radius", diharapkan: "10px" },

  // .card-badan: padding token space-6, jarak baris token space-2.
  { elemen: ".card-badan", properti: "padding-top", diharapkan: "24px" },
  { elemen: ".card-badan", properti: "row-gap", diharapkan: "8px" },

  // .card-judul dan .card-teks: tipografi dari token teks.
  { elemen: ".card-judul", properti: "font-size", diharapkan: "20px" },
  { elemen: ".card-judul", properti: "font-weight", diharapkan: "600" },
  { elemen: ".card-teks", properti: "font-size", diharapkan: "14px" },
  { elemen: ".card-teks", properti: "color", diharapkan: "rgb(82, 82, 91)" },

  // .tab-aktif: garis bawah 2px primer di atas garis transparan.
  { elemen: ".tab-aktif", properti: "color", diharapkan: "rgb(13, 148, 136)" },
  { elemen: ".tab-aktif", properti: "border-bottom-width", diharapkan: "2px" },
  { elemen: ".tab-aktif", properti: "border-bottom-color", diharapkan: "rgb(13, 148, 136)" },

  // .kartu-galeri: bukti perhitungan grid tiga kolom pada wadah 960px.
  { elemen: ".kartu-galeri", properti: "width", diharapkan: "288px" },
];

tests/design-tokens.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Component Library
// Bagian 1: Design Tokens
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/component-library/design-tokens.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // Token tersimpan di :root (elemen html) dan terbaca apa adanya.
  { elemen: "html", properti: "--color-primary", diharapkan: "#0d9488" },
  { elemen: "html", properti: "--space-6", diharapkan: "24px" },
  { elemen: "html", properti: "--radius-md", diharapkan: "10px" },
  { elemen: "html", properti: "--text-base", diharapkan: "16px" },

  // body mengonsumsi token semantik: latar zinc-50, teks zinc-900.
  { elemen: "body", properti: "background-color", diharapkan: "rgb(250, 250, 250)" },
  { elemen: "body", properti: "color", diharapkan: "rgb(24, 24, 27)" },
  { elemen: "body", properti: "font-size", diharapkan: "16px" },

  // .wadah: lebar maksimum 960px dengan padding dari token spasi.
  { elemen: ".wadah", properti: "max-width", diharapkan: "960px" },
  { elemen: ".wadah", properti: "padding-top", diharapkan: "24px" },

  // .badge: latar primer lunak, teks primer, bentuk pil dari token.
  { elemen: ".badge", properti: "background-color", diharapkan: "rgba(13, 148, 136, 0.1)" },
  { elemen: ".badge", properti: "color", diharapkan: "rgb(13, 148, 136)" },
  { elemen: ".badge", properti: "border-radius", diharapkan: "999px" },
  { elemen: ".badge", properti: "font-size", diharapkan: "14px" },
  { elemen: ".badge", properti: "padding-top", diharapkan: "4px" },
];

tests/themes-dan-documentation.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Component Library
// Bagian 4: Themes dan Documentation
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/component-library/themes-dan-documentation.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // Tema terang tetap berlaku di luar wadah data-theme.
  { elemen: "body", properti: "background-color", diharapkan: "rgb(250, 250, 250)" },
  { elemen: ".kartu-terang", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },
  { elemen: ".kartu-terang", properti: "border-top-color", diharapkan: "rgb(228, 228, 231)" },

  // Wadah gelap: token semantik ditimpa, latar dan garis mengikuti.
  { elemen: ".tema-sampel", properti: "background-color", diharapkan: "rgb(24, 24, 27)" },
  { elemen: ".tema-sampel", properti: "padding-top", diharapkan: "24px" },
  { elemen: ".tema-sampel", properti: "border-top-color", diharapkan: "rgb(63, 63, 70)" },

  // Komponen di dalam wadah gelap ikut berganti tanpa aturan baru.
  { elemen: ".kartu-gelap", properti: "background-color", diharapkan: "rgb(39, 39, 42)" },
  { elemen: ".teks-gelap", properti: "color", diharapkan: "rgb(161, 161, 170)" },

  // Custom property terwariskan: badge gelap membaca surface baru.
  { elemen: ".badge-gelap", properti: "--color-surface", diharapkan: "#27272a" },
  { elemen: ".badge-gelap", properti: "background-color", diharapkan: "rgba(20, 184, 166, 0.16)" },

  // Komponen dokumentasi: swatch token berukuran tetap.
  { elemen: ".swatch", properti: "width", diharapkan: "64px" },
  { elemen: ".swatch", properti: "height", diharapkan: "40px" },
  { elemen: ".swatch", properti: "border-radius", diharapkan: "6px" },

  // .contoh-kode memakai primitif zinc: tetap gelap di kedua tema.
  { elemen: ".contoh-kode", properti: "background-color", diharapkan: "rgb(24, 24, 27)" },
  { elemen: ".contoh-kode", properti: "color", diharapkan: "rgb(228, 228, 231)" },
  { elemen: ".contoh-kode", properti: "font-size", diharapkan: "14px" },
  { elemen: ".contoh-kode", properti: "padding-top", diharapkan: "12px" },
  { elemen: ".contoh-kode", properti: "border-radius", diharapkan: "10px" },
];