1. Pengenalan CSS Variables
CSS Custom Properties (juga dikenal sebagai CSS Variables) adalah fitur CSS yang memungkinkan kamu mendefinisikan nilai yang bisa digunakan kembali dan dimodifikasi secara dinamis. Berbeda dengan variabel di preprocessor (Sass, Less), CSS Variables hidup di browser dan bisa diubah secara real-time.
CSS Variables vs Preprocessor Variables
| Aspek | CSS Variables | Sass/Less Variables |
|---|---|---|
| Runtime | Berjalan di browser (live) | Dikompilasi ke CSS (statis) |
| Dynamic | ✅ Bisa diubah via JS/CSS | ❌ Hanya saat compile |
| Cascade | ✅ Mengikuti CSS cascade | ❌ Flat, tidak ada cascade |
| Inheritance | ✅ Diwarisi ke child elements | ❌ Tidak ada inheritance |
| Theming | ✅ Sangat mudah | ⚠️ Perlu rebuild | Fallback | ✅ var(--x, fallback) | ⚠️ Perlu mixin |
| Performance | Native browser | Perlu compilation step |
┌───────────────────────────────────────────────────┐
│ CSS VARIABLES PIPELINE │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ :root │ │ Component │ │
│ │ --color: │────→│ .card { │ │
│ │ #3b82f6 │ │ color: │ │
│ └─────────────┘ │ var(--color)│ │
│ │ └──────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ JavaScript │ │ Browser │ │
│ │ el.style. │────→│ Resolves │ │
│ │ setProperty │ │ Values │ │
│ │('--color', │ │ Live! │ │
│ │ '#10b981') │ └──────────────┘ │
│ └─────────────┘ │
└───────────────────────────────────────────────────┘
2. Dasar Custom Properties
Deklarasi dan Penggunaan
/* Deklarasi variabel di :root (global) */
:root {
--primary-color: #3b82f6;
--secondary-color: #8b5cf6;
--success-color: #10b981;
--danger-color: #ef4444;
--text-color: #1f2937;
--bg-color: #ffffff;
--font-family: 'Inter', -apple-system, sans-serif;
--font-size-base: 16px;
--spacing-unit: 8px;
--border-radius: 8px;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 25px rgba(0,0,0,0.15);
--transition-fast: 0.15s ease;
--transition-normal: 0.3s ease;
--max-width: 1200px;
}
/* Menggunakan variabel */
body {
font-family: var(--font-family);
font-size: var(--font-size-base);
color: var(--text-color);
background-color: var(--bg-color);
line-height: 1.6;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 3);
border: none;
border-radius: var(--border-radius);
transition: all var(--transition-normal);
box-shadow: var(--shadow-sm);
}
.btn-primary:hover {
box-shadow: var(--shadow-md);
}
/* Fallback value jika variabel tidak didefinisikan */
.element {
color: var(--undefined-color, #333);
/* Jika --undefined-color tidak ada, gunakan #333 */
font-size: var(--font-size-base, 16px);
}
/* Multiple fallbacks */
.multi-fallback {
color: var(--custom-color, var(--primary-color, blue));
/* 1. Coba --custom-color
2. Jika tidak ada, coba --primary-color
3. Jika tidak ada, gunakan blue */
}
Property yang Bisa Di-Variabel-kan
/* ✅ Hampir semua property bisa menggunakan variabel */
:root {
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--radius-full: 9999px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
--space-12: 48px;
--space-16: 64px;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.25rem;
}
/* Grid template dengan variabel */
.grid {
display: grid;
grid-template-columns: repeat(var(--grid-cols, 3), 1fr);
gap: var(--space-4);
}
/* Gradient dengan variabel */
.gradient-element {
background: linear-gradient(
var(--gradient-angle, 135deg),
var(--primary-color),
var(--secondary-color)
);
}
/* Media query — ⚠️ Tidak bisa langsung di @media */
/* ❌ */
/* @media (min-width: var(--breakpoint-md)) {} */
/* ✅ Tapi bisa di container queries */
.container {
container-type: inline-size;
}
@container (min-width: 768px) {
.card { grid-template-columns: 1fr 1fr; }
}
/* ✅ Variabel bisa di media query body */
@media (min-width: 768px) {
:root {
--font-size-base: 18px;
--grid-cols: 4;
--spacing-unit: 12px;
}
}
Variabel untuk Komputasi
/* Variabel dalam calc() */
.layout {
--sidebar-width: 280px;
--gap: 24px;
display: grid;
grid-template-columns: var(--sidebar-width) calc(100% - var(--sidebar-width) - var(--gap));
gap: var(--gap);
}
/* Responsive sidebar */
@media (max-width: 768px) {
.layout {
--sidebar-width: 100%;
}
}
/* Variabel dalam color functions */
:root {
--hue: 220;
--saturation: 90%;
--lightness: 55%;
}
.primary {
/* Membuat warna dari HSL */
color: hsl(var(--hue), var(--saturation), var(--lightness));
}
.primary-light {
/* Variasi lebih terang */
color: hsl(var(--hue), var(--saturation), calc(var(--lightness) + 20%));
}
.primary-dark {
/* Variasi lebih gelap */
color: hsl(var(--hue), var(--saturation), calc(var(--lightness) - 15%));
}
/* Clamp dengan variabel */
.responsive-text {
font-size: clamp(
var(--text-min, 14px),
var(--text-fluid, 3vw),
var(--text-max, 24px)
);
}
3. Scoping & Inheritance
CSS Variables mengikuti aturan CSS cascade dan inheritance. Kamu bisa mendefinisikan variabel di berbagai level dan menimpa (override) sesuai kebutuhan.
Global vs Local Scope
/* Global scope (seluruh halaman) */
:root {
--primary: #3b82f6;
--bg: #ffffff;
--text: #1f2937;
}
/* Section scope */
.hero {
--bg: #1e3a5f;
--text: #ffffff;
/* Variabel ini hanya berlaku di .hero dan children-nya */
}
/* Component scope */
.card {
--card-padding: 24px;
--card-bg: var(--bg); /* Menggunakan global */
padding: var(--card-padding);
background: var(--card-bg);
color: var(--text);
}
/* Modifier scope */
.card--featured {
--card-padding: 32px;
--card-bg: linear-gradient(135deg, #667eea, #764ba2);
}
/* Deep override */
.sidebar {
--text: #9ca3af;
color: var(--text);
}
.sidebar .active-link {
--text: var(--primary);
color: var(--text);
}
<!-- HTML: Variabel mengikuti scope terdekat -->
<div class="hero">
<p>Teks putih (dari .hero scope)</p>
<div class="card">
<p>Teks putih (diwarisi dari .hero)</p>
</div>
</div>
<div class="card">
<p>Teks gelap (dari :root scope)</p>
</div>
Cascade Specificity dengan Variabel
/* Variabel mengikuti specificity */
:root {
--color: blue;
}
.section {
--color: green;
}
.highlight {
--color: red;
}
/* Jika elemen memiliki class .section DAN .highlight,
specificity yang lebih tinggi menang */
.text {
color: var(--color);
}
/* .section .highlight .text → --color: red (lebih spesifik) */
/* Ini sangat powerful untuk component theming */
[data-theme="dark"] {
--bg-primary: #0f172a;
--text-primary: #e2e8f0;
}
[data-theme="light"] {
--bg-primary: #ffffff;
--text-primary: #1e293b;
}
/* Komponen tidak perlu tahu tema mana yang aktif */
.card {
background: var(--bg-primary);
color: var(--text-primary);
}
4. Dynamic Variables dengan JavaScript
Salah satu kekuatan terbesar CSS Variables adalah bisa dimanipulasi melalui JavaScript secara real-time. Ini memungkinkan interaksi dinamis tanpa perlu mengubah class atau inline styles.
Membaca dan Menulis CSS Variables
// Membaca variabel dari :root
const root = document.documentElement;
const primaryColor = getComputedStyle(root)
.getPropertyValue('--primary-color').trim();
console.log(primaryColor); // '#3b82f6'
// Menulis variabel ke :root
root.style.setProperty('--primary-color', '#10b981');
// Menghapus variabel
root.style.removeProperty('--primary-color');
// Menulis ke elemen spesifik
const card = document.querySelector('.card');
card.style.setProperty('--card-padding', '32px');
// Menggunakan CSS Variables untuk animasi dari JS
const progress = document.querySelector('.progress-bar');
let value = 0;
function updateProgress() {
value += 1;
progress.style.setProperty('--progress', `${value}%`);
if (value < 100) requestAnimationFrame(updateProgress);
}
// Baca dari elemen tertentu
const el = document.querySelector('.custom-element');
const styles = getComputedStyle(el);
const customVal = styles.getPropertyValue('--my-var').trim();
Interaktif Theme Switcher
// Theme switcher dengan CSS Variables
class ThemeSwitcher {
constructor() {
this.themes = {
light: {
'--bg-primary': '#ffffff',
'--bg-secondary': '#f8fafc',
'--text-primary': '#1e293b',
'--text-secondary': '#64748b',
'--border-color': '#e2e8f0',
'--accent': '#3b82f6'
},
dark: {
'--bg-primary': '#0f172a',
'--bg-secondary': '#1e293b',
'--text-primary': '#e2e8f0',
'--text-secondary': '#94a3b8',
'--border-color': '#334155',
'--accent': '#60a5fa'
},
sepia: {
'--bg-primary': '#f5f0e8',
'--bg-secondary': '#ebe4d4',
'--text-primary': '#433422',
'--text-secondary': '#7a6a52',
'--border-color': '#d4c5a9',
'--accent': '#b8860b'
}
};
}
apply(themeName) {
const theme = this.themes[themeName];
if (!theme) return;
Object.entries(theme).forEach(([prop, value]) => {
document.documentElement.style.setProperty(prop, value);
});
// Juga set data attribute untuk conditional CSS
document.documentElement.setAttribute('data-theme', themeName);
// Simpan preferensi
localStorage.setItem('theme', themeName);
}
init() {
const saved = localStorage.getItem('theme');
const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
this.apply(saved || (prefersDark ? 'dark' : 'light'));
}
}
// Penggunaan
const switcher = new ThemeSwitcher();
switcher.init();
document.getElementById('themeToggle').addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
switcher.apply(current === 'dark' ? 'light' : 'dark');
});
Real-time Color Picker
// Real-time color customization
document.getElementById('colorPicker').addEventListener('input', (e) => {
const color = e.target.value;
// Generate color shades
document.documentElement.style.setProperty('--primary', color);
// Generate lighter shade (gunakan HSL)
const hsl = hexToHSL(color);
document.documentElement.style.setProperty(
'--primary-light',
`hsl(${hsl.h}, ${hsl.s}%, ${Math.min(hsl.l + 20, 95)}%)`
);
document.documentElement.style.setProperty(
'--primary-dark',
`hsl(${hsl.h}, ${hsl.s}%, ${Math.max(hsl.l - 15, 5)}%)`
);
});
function hexToHSL(hex) {
let r = parseInt(hex.slice(1,3), 16) / 255;
let g = parseInt(hex.slice(3,5), 16) / 255;
let b = parseInt(hex.slice(5,7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
case g: h = ((b - r) / d + 2) / 6; break;
case b: h = ((r - g) / d + 4) / 6; break;
}
}
return { h: Math.round(h*360), s: Math.round(s*100), l: Math.round(l*100) };
}
/* CSS yang responsif terhadap perubahan */
.card {
background: var(--primary-light);
border: 2px solid var(--primary);
color: var(--primary-dark);
transition: all 0.3s ease;
}
.card:hover {
background: var(--primary);
color: white;
}
5. Design Tokens
Design tokens adalah unit terkecil dari design system — nilai-nilai atomik yang menyimpan keputusan desain (warna, jarak, ukuran font, dll) dalam format yang konsisten dan reusable.
Tiga Layer Design Tokens
/* ============================================
LAYER 1: PRIMITIVE TOKENS
Nilai mentah yang tidak bermakna
============================================ */
:root {
/* Colors */
--blue-50: #eff6ff;
--blue-100: #dbeafe;
--blue-200: #bfdbfe;
--blue-300: #93c5fd;
--blue-400: #60a5fa;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-700: #1d4ed8;
--blue-800: #1e40af;
--blue-900: #1e3a8a;
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
/* Spacing scale (4px base) */
--space-0: 0;
--space-0-5: 2px;
--space-1: 4px;
--space-1-5: 6px;
--space-2: 8px;
--space-2-5: 10px;
--space-3: 12px;
--space-3-5: 14px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 28px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
--space-20: 80px;
--space-24: 96px;
/* Font sizes */
--text-2xs: 0.625rem; /* 10px */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
--text-5xl: 3rem; /* 48px */
/* Font weights */
--weight-normal: 400;
--weight-medium: 500;
--weight-semibold: 600;
--weight-bold: 700;
/* Border radius */
--radius-none: 0;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-2xl: 24px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1);
--shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1);
/* Z-index scale */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-tooltip: 1070;
--z-toast: 1080;
}
/* ============================================
LAYER 2: SEMANTIC TOKENS
Nilai yang bermakna dan kontekstual
============================================ */
:root {
/* Semantic colors */
--color-primary: var(--blue-500);
--color-primary-hover: var(--blue-600);
--color-primary-active: var(--blue-700);
--color-primary-light: var(--blue-50);
--color-primary-subtle: var(--blue-100);
--color-text: var(--gray-900);
--color-text-secondary: var(--gray-600);
--color-text-muted: var(--gray-400);
--color-text-inverse: var(--gray-50);
--color-bg: var(--gray-50);
--color-bg-elevated: white;
--color-bg-sunken: var(--gray-100);
--color-border: var(--gray-200);
--color-border-strong: var(--gray-300);
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
/* Semantic spacing */
--spacing-page: var(--space-6);
--spacing-section: var(--space-16);
--spacing-component: var(--space-4);
--spacing-element: var(--space-2);
/* Semantic typography */
--font-heading: 'Plus Jakarta Sans', sans-serif;
--font-body: 'Inter', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--line-height-heading: 1.2;
--line-height-body: 1.6;
}
/* ============================================
LAYER 3: COMPONENT TOKENS
Nilai spesifik untuk komponen
============================================ */
:root {
/* Button */
--btn-height-sm: 32px;
--btn-height-md: 40px;
--btn-height-lg: 48px;
--btn-padding-sm: var(--space-2) var(--space-3);
--btn-padding-md: var(--space-2-5) var(--space-5);
--btn-padding-lg: var(--space-3) var(--space-6);
--btn-radius: var(--radius-md);
--btn-font-weight: var(--weight-medium);
/* Card */
--card-padding: var(--space-6);
--card-radius: var(--radius-lg);
--card-shadow: var(--shadow-md);
--card-bg: var(--color-bg-elevated);
--card-border: 1px solid var(--color-border);
/* Input */
--input-height: 40px;
--input-padding: var(--space-2-5) var(--space-3);
--input-radius: var(--radius-md);
--input-border: 1px solid var(--color-border);
--input-bg: var(--color-bg-elevated);
/* Nav */
--nav-height: 64px;
--nav-bg: var(--color-bg-elevated);
--nav-shadow: var(--shadow-sm);
}
6. Theming: Light & Dark Mode
Membangun sistem tema yang fleksibel adalah salah satu use case terbaik untuk CSS Variables. Kita akan membangun dark mode yang proper dengan transisi halus.
Theme System Lengkap
/* ============================================
THEME SYSTEM
Menggunakan data-theme attribute pada html
============================================ */
/* Base (Light Theme) */
:root,
[data-theme="light"] {
/* Backgrounds */
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-tertiary: #f1f5f9;
--bg-elevated: #ffffff;
--bg-overlay: rgba(0, 0, 0, 0.5);
/* Text */
--text-primary: #0f172a;
--text-secondary: #475569;
--text-tertiary: #94a3b8;
--text-inverse: #ffffff;
--text-link: #2563eb;
--text-link-hover: #1d4ed8;
/* Borders */
--border-primary: #e2e8f0;
--border-secondary: #cbd5e1;
--border-focus: #3b82f6;
/* Interactive */
--interactive-primary: #3b82f6;
--interactive-primary-hover: #2563eb;
--interactive-secondary: #f1f5f9;
--interactive-secondary-hover: #e2e8f0;
/* Status */
--status-success: #10b981;
--status-success-bg: #ecfdf5;
--status-warning: #f59e0b;
--status-warning-bg: #fffbeb;
--status-error: #ef4444;
--status-error-bg: #fef2f2;
--status-info: #3b82f6;
--status-info-bg: #eff6ff;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
/* Dark Theme */
[data-theme="dark"] {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--bg-elevated: #1e293b;
--bg-overlay: rgba(0, 0, 0, 0.7);
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-tertiary: #64748b;
--text-inverse: #0f172a;
--text-link: #60a5fa;
--text-link-hover: #93bbfd;
--border-primary: #334155;
--border-secondary: #475569;
--border-focus: #60a5fa;
--interactive-primary: #60a5fa;
--interactive-primary-hover: #93c5fd;
--interactive-secondary: #334155;
--interactive-secondary-hover: #475569;
--status-success: #34d399;
--status-success-bg: #064e3b;
--status-warning: #fbbf24;
--status-warning-bg: #78350f;
--status-error: #f87171;
--status-error-bg: #7f1d1d;
--status-info: #60a5fa;
--status-info-bg: #1e3a8a;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
}
/* Transisi tema yang halus */
*,
*::before,
*::after {
transition: background-color 0.3s ease,
color 0.3s ease,
border-color 0.3s ease,
box-shadow 0.3s ease;
}
/* Menggunakan tema di komponen */
body {
background-color: var(--bg-primary);
color: var(--text-primary);
}
.card {
background: var(--bg-elevated);
border: 1px solid var(--border-primary);
box-shadow: var(--shadow-md);
color: var(--text-primary);
}
.alert-success {
background: var(--status-success-bg);
color: var(--status-success);
border: 1px solid var(--status-success);
}
a {
color: var(--text-link);
}
a:hover {
color: var(--text-link-hover);
}
Auto Dark Mode dengan System Preference
/* Menggunakan prefers-color-scheme */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
/* Terapkan dark theme kecuali user memilih light */
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--text-primary: #f1f5f9;
/* ... (sama seperti [data-theme="dark"]) */
}
}
/* Toggle button */
<button id="themeToggle" aria-label="Ganti tema">
<span class="theme-icon-light">☀️</span>
<span class="theme-icon-dark">🌙</span>
</button>
<style>
[data-theme="light"] .theme-icon-dark { display: none; }
[data-theme="dark"] .theme-icon-light { display: none; }
</style>
<script>
const html = document.documentElement;
const toggle = document.getElementById('themeToggle');
// Inisialisasi
const saved = localStorage.getItem('theme');
if (saved) {
html.setAttribute('data-theme', saved);
} else {
const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
html.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
}
toggle.addEventListener('click', () => {
const current = html.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
</script>
Multi-Theme (Brand Themes)
/* Theme: Brand Blue */
[data-theme="brand-blue"] {
--primary-hue: 220;
--primary-sat: 90%;
--primary-lum: 55%;
}
/* Theme: Brand Green */
[data-theme="brand-green"] {
--primary-hue: 160;
--primary-sat: 80%;
--primary-lum: 45%;
}
/* Theme: Brand Purple */
[data-theme="brand-purple"] {
--primary-hue: 270;
--primary-sat: 75%;
--primary-lum: 55%;
}
/* Derive semua warna dari satu hue */
:root {
--primary: hsl(var(--primary-hue), var(--primary-sat), var(--primary-lum));
--primary-hover: hsl(var(--primary-hue), var(--primary-sat), calc(var(--primary-lum) - 10%));
--primary-light: hsl(var(--primary-hue), var(--primary-sat), calc(var(--primary-lum) + 30%));
--primary-subtle: hsl(var(--primary-hue), var(--primary-sat), calc(var(--primary-lum) + 40%));
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover {
background: var(--primary-hover);
}
7. Komponen Reusable dengan Variables
Button Component System
/* Button base */
.btn {
--btn-bg: var(--interactive-primary);
--btn-text: white;
--btn-border: transparent;
--btn-hover-bg: var(--interactive-primary-hover);
--btn-height: var(--btn-height-md);
--btn-padding: var(--btn-padding-md);
--btn-radius: var(--radius-md);
--btn-font-size: var(--text-sm);
--btn-font-weight: var(--weight-medium);
--btn-shadow: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
height: var(--btn-height);
padding: var(--btn-padding);
background: var(--btn-bg);
color: var(--btn-text);
border: 1px solid var(--btn-border);
border-radius: var(--btn-radius);
font-size: var(--btn-font-size);
font-weight: var(--btn-font-weight);
cursor: pointer;
transition: all var(--transition-fast);
}
.btn:hover {
background: var(--btn-hover-bg);
}
/* Size variants */
.btn--sm {
--btn-height: var(--btn-height-sm);
--btn-padding: var(--btn-padding-sm);
--btn-font-size: var(--text-xs);
}
.btn--lg {
--btn-height: var(--btn-height-lg);
--btn-padding: var(--btn-padding-lg);
--btn-font-size: var(--text-base);
}
/* Style variants — override variabel! */
.btn--secondary {
--btn-bg: var(--interactive-secondary);
--btn-text: var(--text-primary);
--btn-hover-bg: var(--interactive-secondary-hover);
}
.btn--ghost {
--btn-bg: transparent;
--btn-text: var(--text-primary);
--btn-hover-bg: var(--interactive-secondary);
}
.btn--danger {
--btn-bg: var(--status-error);
--btn-hover-bg: #dc2626;
}
.btn--outline {
--btn-bg: transparent;
--btn-text: var(--interactive-primary);
--btn-border: var(--interactive-primary);
--btn-hover-bg: var(--interactive-primary);
--btn-hover-text: white;
}
/* Penggunaan */
<button class="btn btn--primary">Primary</button>
<button class="btn btn--secondary btn--sm">Small Secondary</button>
<button class="btn btn--danger btn--lg">Large Danger</button>
<button class="btn btn--outline">Outline</button>
Card Component System
.card {
--card-bg: var(--bg-elevated);
--card-border: var(--border-primary);
--card-radius: var(--radius-lg);
--card-padding: var(--space-6);
--card-shadow: var(--shadow-md);
--card-hover-translate: -4px;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: var(--card-radius);
padding: var(--card-padding);
box-shadow: var(--card-shadow);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(var(--card-hover-translate));
box-shadow: var(--shadow-lg);
}
/* Card variants */
.card--flat {
--card-shadow: none;
--card-hover-translate: 0;
}
.card--accent {
--card-border: var(--primary);
border-top: 3px solid var(--primary);
}
.card--compact {
--card-padding: var(--space-4);
}
.card--elevated {
--card-shadow: var(--shadow-xl);
}
/* Card sections */
.card__header {
padding-bottom: var(--space-4);
border-bottom: 1px solid var(--card-border);
margin-bottom: var(--space-4);
}
.card__body {
flex: 1;
}
.card__footer {
padding-top: var(--space-4);
border-top: 1px solid var(--card-border);
margin-top: var(--space-4);
display: flex;
gap: var(--space-3);
justify-content: flex-end;
}
8. Teknik Advanced
Variabel dengan Type Hints (Houdini)
/* CSS Houdini: Registered custom properties */
/* Browser yang mendukung bisa menggunakan animasi */
CSS.registerProperty({
name: '--gradient-angle',
syntax: '<angle>',
inherits: false,
initialValue: '0deg'
});
CSS.registerProperty({
name: '--gradient-color-1',
syntax: '<color>',
inherits: false,
initialValue: '#3b82f6'
});
CSS.registerProperty({
name: '--gradient-color-2',
syntax: '<color>',
inherits: false,
initialValue: '#8b5cf6'
});
.gradient-animated {
background: conic-gradient(
from var(--gradient-angle),
var(--gradient-color-1),
var(--gradient-color-2),
var(--gradient-color-1)
);
animation: rotateGradient 3s linear infinite;
}
@keyframes rotateGradient {
from { --gradient-angle: 0deg; }
to { --gradient-angle: 360deg; }
}
Variabel untuk Responsive Spacing
/* Fluid spacing system */
:root {
/* Menggunakan clamp() untuk spacing responsif */
--space-fluid-xs: clamp(4px, 0.5vw, 8px);
--space-fluid-sm: clamp(8px, 1vw, 16px);
--space-fluid-md: clamp(16px, 2vw, 32px);
--space-fluid-lg: clamp(32px, 4vw, 64px);
--space-fluid-xl: clamp(48px, 6vw, 96px);
/* Fluid typography */
--text-fluid-sm: clamp(0.875rem, 1vw + 0.5rem, 1rem);
--text-fluid-base: clamp(1rem, 1.2vw + 0.5rem, 1.125rem);
--text-fluid-lg: clamp(1.25rem, 2vw + 0.5rem, 1.75rem);
--text-fluid-xl: clamp(1.5rem, 3vw + 0.5rem, 2.5rem);
--text-fluid-2xl: clamp(2rem, 4vw + 0.5rem, 3.5rem);
--text-fluid-3xl: clamp(2.5rem, 5vw + 0.5rem, 5rem);
}
.section {
padding: var(--space-fluid-xl) var(--space-fluid-md);
}
.section h2 {
font-size: var(--text-fluid-2xl);
margin-bottom: var(--space-fluid-md);
}
Variabel untuk Grid Layout
/* Grid system dengan variabel */
.grid {
--grid-cols: 12;
--grid-gap: var(--space-4);
display: grid;
grid-template-columns: repeat(var(--grid-cols), 1fr);
gap: var(--grid-gap);
}
/* Span helpers */
.col-1 { grid-column: span 1; }
.col-2 { grid-column: span 2; }
.col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-5 { grid-column: span 5; }
.col-6 { grid-column: span 6; }
.col-7 { grid-column: span 7; }
.col-8 { grid-column: span 8; }
.col-9 { grid-column: span 9; }
.col-10 { grid-column: span 10; }
.col-11 { grid-column: span 11; }
.col-12 { grid-column: span 12; }
/* Responsive: ubah variabel */
@media (max-width: 768px) {
.grid {
--grid-cols: 4;
--grid-gap: var(--space-3);
}
.col-md-4 { grid-column: span 4; }
}
@media (max-width: 480px) {
.grid {
--grid-cols: 2;
}
.col-sm-2 { grid-column: span 2; }
}
9. Best Practices
- 📌 Nama yang bermakna: Gunakan prefix seperti
--color-,--space-,--font- - 📌 3 Layer tokens: Primitive → Semantic → Component
- 📌 Selalu berikan fallback:
var(--color, #333) - 📌 Gunakan :root untuk global, komponen spesifik untuk local
- 📌 Hindari deep nesting:
var(--a, var(--b, var(--c, fallback))) - 📌 Konsisten dengan unit: Simpan angka saja, gunakan
calc() - 📌 Dokumentasikan: Buat style guide yang menunjukkan semua tokens
- 📌 Jangan over-engineer: Variabel hanya untuk nilai yang benar-benar berubah
- ❌ CSS Variables tidak bisa digunakan dalam
@mediaquery sebagai nilai breakpoint - ❌ CSS Variables tidak bisa digunakan sebagai nama property (
var(--prop): valuetidak valid) - ❌ CSS Variables tidak mempengaruhi specificity — specificity ditentukan oleh selector
- ⚠️ Animasi CSS Variables hanya bekerja dengan
CSS.registerProperty()(Houdini)
10. Quiz Pemahaman
Uji pemahamanmu tentang CSS Variables & Design System: