CSS Variables
CSS custom properties: deklarasi, inheritance, scoping, cascade, fallback, dan update runtime via Alpine.js.
Anatomi Custom Property
CSS custom property terdiri dari dua bagian: deklarasi (nama + nilai)
dan penggunaan via fungsi var(). Nama selalu diawali
dua tanda hubung (--) dan case-sensitive.
Deklarasi
:root { --color-brand: #3b82f6; --spacing-card: 1.5rem; --radius-card: var(--radius-sm); }
Penggunaan via var()
.card { color: var(--color-brand); padding: var(--spacing-card); border-radius: var(--radius-card); }
Konvensi Penamaan Proyek ini
Inheritance & Cascade
Custom properties mengikuti cascade CSS normal — dan juga diwariskan ke elemen anak secara default. Deklarasi yang lebih spesifik atau lebih dekat ke elemen menang. Ini yang membuat scoped theming bisa dilakukan hanya dengan CSS.
--color-brand: #3b82f6--spacing-base: 1rem
/* tidak redefinisi → --color-brand masih #3b82f6 */
--color-brand: #059669 ← baru di sini/* --spacing-base masih diwarisi: 1rem */
color: var(--color-brand) → #059669
bukan #3b82f6 — scope .card yang dipakai
:root { --color-brand: #3b82f6; } /* Override di scope .card — hanya berlaku untuk .card dan semua anaknya */ .card { --color-brand: #059669; } /* .card__title mewarisi nilai dari .card, bukan :root */ .card__title { color: var(--color-brand); } /* → #059669 */
Fallback Values
var() menerima argumen kedua sebagai fallback — dipakai jika properti
tidak terdefinisi. Fallback bisa bertingkat dan bahkan mengandung var() lain.
Berguna untuk komponen yang bisa dipakai di luar sistem token.
✓ Properti terdefinisi → pakai nilai aslinya
⚠ Properti tidak ada → pakai fallback literal #6b7280
→ x tidak ada → y tidak ada → pakai --color-accent-b1
/* Fallback literal */ color: var(--color-brand, #3b82f6); /* Fallback ke var lain */ color: var(--color-brand, var(--color-accent-b1)); /* Fallback bertingkat tiga */ color: var(--x, var(--y, var(--color-accent-b1, #93c5fd))); /* TIDAK bekerja: fallback tidak bisa berisi titik-koma */ color: var(--x, red; background: blue); /* ← invalid */
Component-scoped Variables
Pattern terbaik untuk komponen yang bisa dikustomisasi dari luar: definisikan variabel lokal (prefixed dengan nama komponen) di root komponen, pakai fallback ke token global. Konsumer cukup override variabel lokal tanpa menyentuh internals.
Kedua card memakai HTML yang identik — perbedaan hanya dari override variabel lokal di atribut style.
/* Definisi variabel lokal di root komponen */ .card { --card-bg: var(--color-bg-b1); --card-border: var(--color-bg-b4); --card-accent: var(--color-accent-d8); background: var(--card-bg); border: 1px solid var(--card-border); } /* Konsumer: override tanpa menyentuh internals .card */ .card--danger { --card-bg: oklch(from var(--color-error) l c h / 0.06); --card-border: oklch(from var(--color-error) l c h / 0.35); --card-accent: var(--color-error); }
var() dalam calc() & Fungsi CSS Modern
Custom properties bisa digunakan di dalam calc(), clamp(),
oklch(), dan fungsi CSS lainnya — termasuk untuk komputasi spasial
dan manipulasi warna tanpa preprocessor.
calc() — spasi turunan dari token
oklch() — variasi opacity & lightness dari satu token
clamp() — fluid sizing dari token
clamp(var(--text-h4), 3vw, var(--text-h1)) — ubah lebar jendela untuk melihat perubahan
/* calc() — komputasi dari token */ border-radius: calc(var(--radius-md) * 0.5); padding: calc(var(--spacing-section-y) / 2); /* oklch() — opacity dari token warna */ background: oklch(from var(--color-accent-d8) l c h / 0.15); /* clamp() — fluid sizing dengan batas min/max dari token */ font-size: clamp(var(--text-h4), 3vw, var(--text-h1));
Runtime Update via Alpine.js
Custom properties dapat diperbarui di runtime langsung dari JavaScript — perubahan otomatis menyebar ke semua elemen yang menggunakan properti tersebut. Demo di bawah memodifikasi variabel lokal pada elemen tanpa menyentuh class.
Controls
Computed Values
Live Preview
// Update via JavaScript — menyebar ke semua konsumer secara instan document.documentElement.style.setProperty('--color-brand', '#059669'); // Alpine.js — update pada elemen lokal (bukan :root) // x-init untuk inisialisasi awal, @change untuk reaktivitas x-data="{ brand: '#3b82f6' }" x-init="$el.style.setProperty('--live-brand', brand)" @change="$el.style.setProperty('--live-brand', brand)" // Baca nilai saat ini via JavaScript const val = getComputedStyle(document.documentElement) .getPropertyValue('--color-brand').trim();
Cascade Layers & Specificity
Custom properties mengikuti aturan specificity CSS biasa. Properti pada selector
lebih spesifik menang, terlepas dari urutan deklarasi. !important
juga berlaku — tapi jarang diperlukan dengan arsitektur token yang baik.
:root
→
--color-brand: #94a3b8
div
→
--color-brand: #6366f1
.card
→
--color-brand: #3b82f6
.card.featured
→
--color-brand: #2563eb
#hero .card
→
--color-brand: #1d4ed8
style=""
→
--color-brand: #1e40af
/* Urutan menang: inline > ID > kelas > elemen > :root */ :root { --color-brand: #94a3b8; } /* (0,0,0) */ div { --color-brand: #6366f1; } /* (0,0,1) */ .card { --color-brand: #3b82f6; } /* (0,1,0) */ .card.featured { --color-brand: #2563eb; } /* (0,2,0) */ #hero .card { --color-brand: #1d4ed8; } /* (1,1,0) */ /* Inline style — prioritas tertinggi (sebelum !important) */ <div style="--color-brand: #1e40af">…</div>
Batasan var()
Ada beberapa hal yang tidak bisa dilakukan dengan CSS custom properties. Mengetahui batasannya penting agar tidak menghabiskan waktu debugging perilaku yang memang tidak didukung.
✗ var(--my-prop): red
Nama properti harus literal, bukan variabel.
✗ color: var(--x, red; background: blue)
Titik-koma mengakhiri deklarasi — fallback tidak bisa punya semikolon.
✗ @media (min-width: var(--bp-md)) { … }
Media queries dievaluasi sebelum nilai variabel tersedia.
✗ color: var(--undefined-prop)
Jika tidak ada fallback dan prop tidak terdefinisi, properti menjadi invalid-at-computed-value-time dan warisi atau pakai initial.