Komponen dasar dari dua bagian pertama kini dirangkai menjadi komponen komposit: kartu yang menampung badge dan tombol, navigation bar yang membungkus logo dan menu, serta tab untuk berpindah section dokumentasi. Inilah pola kerja utama sebuah component library — komponen baru hampir selalu komposisi dari komponen lama, ditambah satu atau dua keputusan layout. Kita juga menyiapkan grid galeri yang dipakai Bagian 4 untuk memajang kartu komponen di halaman dokumentasi.

Tujuan Bagian dan Prasyarat #

Target bagian ini pada styles/komponen.css:

  • .card dengan elemen pendamping .card-badan, .card-judul, .card-teks, dan .card-aksi, plus varian .card-shadow.
  • Navigation: .navbar dengan position: sticky, .nav-brand, .nav-menu, dan .nav-tautan.
  • Tab: .tabs, .tab, dan .tab-aktif dengan garis bawah 2px yang tidak menggeser layout.
  • .grid-kartu: grid tiga kolom dengan kartu selebar 288px pada wadah 960px.

Konsep layoutnya bersumber dari Flex Container dan Items , Card Grid , dan Sticky Header . Penataan kelas mengikuti prinsip Component-Based CSS , dengan penamaan dua tingkat yang dibedah di akhir bagian.

Kartu Konten #

Kartu memakai flex-direction: column agar elemen aksi selalu menempel di dasar ketika kartu diregangkan oleh grid, dan overflow: hidden agar sudut membulat memotong isi:

css styles/komponen.css — Bagian 3 (kartu)
.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);
}

Kartu contoh merangkai badge dari Bagian 1 dan tombol dari Bagian 2:

html index.html — kartu komponen
<article class="card card-shadow">
  <div class="card-badan">
    <span class="badge">Komponen</span>
    <h3 class="card-judul">Button</h3>
    <p class="card-teks">Tiga varian warna dan dua ukuran, lengkap dengan status disabled.</p>
    <div class="card-aksi">
      <button class="btn btn-kedua btn-kecil">Lihat Detail</button>
    </div>
  </div>
</article>

Hasil di Browser:

text
Kartu berlatar rgb(255, 255, 255) dengan border rgb(228, 228, 231)
dan radius 10px; badan kartu berpadding 24px dengan jarak antar
elemen 8px. Judul terukur 20px dengan ketebalan 600, teks
penjelas 14px berwarna rgb(82, 82, 91).

Navigation bar menempel di atas halaman lewat position: sticky, sementara tab memakai trik garis bawah transparan agar tinggi tab aktif dan pasif selalu sama:

css styles/komponen.css — Bagian 3 (navigasi dan tab)
.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);
}
html index.html — navigation bar dan tab
<header class="navbar">
  <a class="nav-brand" href="#">
    <img src="assets/logo-komponen.svg" width="24" height="24" alt="">
    Rakit UI
  </a>
  <nav>
    <ul class="nav-menu">
      <li><a class="nav-tautan" href="#komponen">Komponen</a></li>
      <li><a class="nav-tautan" href="#token">Token</a></li>
    </ul>
  </nav>
</header>

<div class="tabs" role="tablist">
  <button class="tab tab-aktif" role="tab">Buttons</button>
  <button class="tab" role="tab">Inputs</button>
</div>

Grid Galeri Komponen #

Halaman dokumentasi memajang kartu komponen dalam grid tiga kolom; Bagian 4 mengisinya dengan kartu nyata:

css styles/komponen.css — Bagian 3 (galeri)
.grid-kartu {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
  margin-top: var(--space-6);
}

Bedah Keputusan Layout #

  1. position: sticky dengan top: 0 dan z-index: 100: navbar mengalir dalam dokumen namun menempel saat digulir; z-index tinggi menjaga navbar berada di atas kartu dan badge. Perbedaannya dengan fixed dibahas di chapter Sticky Header.
  2. overflow: hidden pada .card: tanpa properti ini, konten yang menonjol — misalnya gambar header — menembus sudut membulat kartu. Dengan radius di kartu induk, isi cukup dipotong oleh kartu itu sendiri.
  3. Garis bawah transparan pada .tab: tab pasif tetap membawa border-bottom: 2px solid transparent, sehingga saat kelas .tab-aktif memunculkan garis primer, tinggi tab tidak berubah sedikit pun. Ini pencegahan layout shift paling murah.
  4. Matematika .grid-kartu: wadah 960px dikurangi padding 24px dua sisi menyisakan 912px; dua gap 24px memakan 48px; sisa 864px dibagi tiga kolom 1fr menghasilkan kartu selebar 288px — angka yang diverifikasi probe.
  5. Penamaan dua tingkat: pola <blok>-<elemen> (.card-badan, .nav-menu) dipilih agar keterbacaan tetap tinggi tanpa penuh garis bawah ganda; varian BEM penuh (card__badan) diulas tersendiri pada chapter BEM .

Uji Bagian Ini #

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

ElemenPropertiDiharapkan
.navbarpositionsticky
.navbartop0px
.navbarz-index100
.nav-menucolumn-gap24px
.cardbackground-colorrgb(255, 255, 255)
.cardborder-radius10px
.card-badanpadding-top24px
.card-badanrow-gap8px
.card-judulfont-size20px
.card-tekscolorrgb(82, 82, 91)
.tab-aktifborder-bottom-width2px
.tab-aktifcolorrgb(13, 148, 136)
.kartu-galeriwidth288px

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" },
];