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:
.btndengan tiga varian warna:.btn-primer,.btn-kedua, dan.btn-hantu.- Dua kelas ukuran:
.btn-kecildan.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.
.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:
<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:
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:
.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);
}<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 #
font: inheritpada.btndan.input: elemenbuttondaninputtidak mewarisi font dari induk secara bawaan. Tanpa baris ini, teks tombol tetap memakai font sistem dan ukuran bawaan browser meskibodysudah diatur.- Urutan varian sebelum ukuran:
.btn-primerdan.btn-kecilspecificity-nya sama, sehingga urutan penulisan yang menentukan. Kelas ukuran ditulis terakhir karena menimpapaddingdanfont-sizedari kelas dasar — mekanisme cascade yang dibahas di Avoiding Specificity Conflicts . :disabled, bukan kelas.disabled: pseudo-class ini hanya berlaku pada kontrol yang benar-benar dinonaktifkan lewat atributdisabled, sehingga gaya tidak mungkin meleset dari perilaku aksesibilitasnya..fieldberbasis grid:row-gap: 8pxmengatur jarak antar baris label-input-hint sekaligus;margin-bottom: 16pxmemisahkan antar field. Dua jarak, dua properti, tanpa angka literal.- Status sebagai pasangan kelas:
.input-galatselalu berpasangan dengan.hint-galatagar border dan pesan berbicara warna yang sama (--color-danger). Versi pseudo-class:invaliddibahas 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:
| Elemen | Properti | Diharapkan |
|---|---|---|
.btn-primer | background-color | rgb(13, 148, 136) |
.btn-primer | color | rgb(255, 255, 255) |
.btn-primer | padding-top | 8px |
.btn-primer | border-radius | 10px |
.btn-besar | font-size | 20px |
.btn-besar | padding-top | 12px |
.btn-kedua | border-top-color | rgb(228, 228, 231) |
.btn (disabled) | opacity | 0.55 |
.btn (disabled) | cursor | not-allowed |
.input | border-top-color | rgb(228, 228, 231) |
.input | border-radius | 6px |
.input | font-size | 16px |
.input-galat | border-top-color | rgb(220, 38, 38) |
.hint | font-size | 12px |
.field | row-gap | 8px |
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
styles/komponen.css
Unduh berkas/* =========================================================
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// ============================================================
// 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// ============================================================
// 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// ============================================================
// 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// ============================================================
// 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" },
];