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

--color-{role} Warna semantik
--color-{scale}-{n} Warna skala (bg-b1, accent-d3)
--text-{size} Ukuran font
--leading-{role} Line height
--tracking-{role} Letter spacing
--spacing-{role} Spacing layout
--radius-{size} Border radius
--shadow-{size} Box shadow
--z-{layer} Z-index layer
--duration-{speed} Transition duration
--easing-{curve} Transition easing
--font-{role} Font family

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.

:root
--color-brand: #3b82f6
--spacing-base: 1rem
.page-section — mewarisi dari :root
/* tidak redefinisi → --color-brand masih #3b82f6 */
.card — scoped override
--color-brand: #059669 ← baru di sini
/* --spacing-base masih diwarisi: 1rem */
.card__title — mewarisi dari .card
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.

var(--color-accent-d8, #374151)
✓ Properti terdefinisi → pakai nilai aslinya
var(--color-brand-undefined, #6b7280)
⚠ Properti tidak ada → pakai fallback literal #6b7280
var(--color-x, var(--color-y, var(--color-accent-b1, #93c5fd)))
→ 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.

Default Card
Pakai token global via fallback.
default
Danger Card
Override 3 variabel lokal saja.
danger

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

×0.5 6px
×1 12px
×1.5 18px
×2 24px

oklch() — variasi opacity & lightness dari satu token

10%
25%
50%
75%
100%

clamp() — fluid sizing dari token

Fluid Heading

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-brand
--live-radius
--live-size

Live Preview

Card Title
Semua properti dikendalikan oleh CSS custom properties yang diperbarui secara runtime.
// 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.

kalah :root → --color-brand: #94a3b8
kalah div → --color-brand: #6366f1
kalah .card → --color-brand: #3b82f6
kalah .card.featured → --color-brand: #2563eb
kalah #hero .card → --color-brand: #1d4ed8
MENANG 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.

Tidak bisa dipakai sebagai property name
✗ var(--my-prop): red Nama properti harus literal, bukan variabel.
Tidak bisa mengandung titik-koma di fallback
✗ color: var(--x, red; background: blue) Titik-koma mengakhiri deklarasi — fallback tidak bisa punya semikolon.
Tidak bisa dipakai sebagai media query value
✗ @media (min-width: var(--bp-md)) { … } Media queries dievaluasi sebelum nilai variabel tersedia.
Nilai awal bukan string kosong — tapi ada (whitespace)
✗ color: var(--undefined-prop) Jika tidak ada fallback dan prop tidak terdefinisi, properti menjadi invalid-at-computed-value-time dan warisi atau pakai initial.