CSS Custom Properties (Biến CSS)
Biến CSS (CSS Custom Properties) cho phép định nghĩa giá trị một lần và tái sử dụng nhiều nơi. Khác với biến SCSS/LESS, biến CSS là runtime — thay đổi được bằng JavaScript và kế thừa theo cascade.
Khai báo và sử dụng
/* Khai báo: tên bắt đầu bằng -- */
:root {
--primary: #4472c4;
--spacing-md: 1rem;
--font-size-base: 16px;
}
/* Sử dụng với var() */
.btn {
background: var(--primary);
padding: var(--spacing-md);
font-size: var(--font-size-base);
}
/* Giá trị fallback */
.text { color: var(--text-color, #333); }
Phạm vi (Scope)
Biến khai báo trong :root có phạm vi toàn cục. Có thể ghi đè trong selector cụ thể:
:root { --accent: #4472c4; }
.card-warning { --accent: #e09a00; }
/* Chỉ trong .card-warning, --accent là màu vàng */
Dark mode với biến CSS
:root {
--bg: #ffffff;
--text: #333333;
--surface: #f5f5f5;
}
[data-theme="dark"] {
--bg: #1a1a2e;
--text: #e0e0e0;
--surface: #2d2d3a;
}
body {
background: var(--bg);
color: var(--text);
}
Chuyển theme chỉ cần: document.documentElement.setAttribute('data-theme', 'dark')
Ưu điểm so với biến SCSS
| CSS Custom Properties | SCSS Variables | |
|---|---|---|
| Thay đổi runtime (JS) | ✅ | ❌ |
| Kế thừa theo CSS cascade | ✅ | ❌ |
| Hỗ trợ media query | ✅ | Chỉ compile-time |
| DevTools hiển thị | ✅ | ❌ (đã compile) |
| Cần build step | ❌ | ✅ |
Demo: Dark mode toggle với CSS variables
<div class="demo-container">
<button class="theme-toggle">🌙 Chuyển Dark Mode</button>
<div class="card">
<h3>Card component</h3>
<p>Nội dung card sử dụng CSS variables cho màu sắc. Nhấn nút trên để chuyển theme.</p>
<a href="#" class="card-link">Xem thêm →</a>
</div>
</div>
:root {
--bg: #f5f5f5;
--text: #333;
--surface: #fff;
--accent: #4472c4;
--border: #ddd;
}
.demo-container.dark {
--bg: #1a1a2e;
--text: #e0e0e0;
--surface: #2d2d3a;
--accent: #79b8ff;
--border: #444;
}
.demo-container {
background: var(--bg);
color: var(--text);
padding: 1.5rem;
min-height: 160px;
border-radius: 8px;
transition: background 0.3s, color 0.3s;
font-family: sans-serif;
}
.theme-toggle {
background: var(--accent);
color: white;
border: none;
padding: .5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: .9rem;
margin-bottom: 1rem;
transition: background 0.3s;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
transition: background 0.3s, border-color 0.3s;
}
.card h3 { margin: 0 0 .5rem; color: var(--accent); }
.card p { margin: 0 0 .75rem; font-size: .9rem; }
.card-link { color: var(--accent); text-decoration: none; font-size: .9rem; }
.card-link:hover { text-decoration: underline; }
document.querySelector('.theme-toggle').addEventListener('click', function() {
document.querySelector('.demo-container').classList.toggle('dark');
});
Demo: Hai theme khác nhau với cùng component
<div class="themes-side-by-side">
<div class="theme-wrap" id="theme-ocean">
<p class="theme-label">Ocean theme</p>
<div class="t-card">
<div class="t-badge">MỚI</div>
<h4>Tiêu đề card</h4>
<p>Nội dung card dùng CSS variables. Cùng HTML và CSS gốc, chỉ override variables.</p>
<button class="t-btn">Hành động</button>
</div>
</div>
<div class="theme-wrap" id="theme-forest">
<p class="theme-label">Forest theme</p>
<div class="t-card">
<div class="t-badge">MỚI</div>
<h4>Tiêu đề card</h4>
<p>Nội dung card dùng CSS variables. Cùng HTML và CSS gốc, chỉ override variables.</p>
<button class="t-btn">Hành động</button>
</div>
</div>
</div>
body { font-family: sans-serif; padding: 1rem; }
/* Component styles dùng variables */
.t-card {
position: relative;
background: var(--card-bg);
border: 2px solid var(--card-border);
border-radius: 10px; padding: 1.25rem;
box-shadow: 0 4px 12px var(--card-shadow);
}
.t-badge {
position: absolute; top: -10px; right: 12px;
background: var(--accent); color: white;
padding: 2px 10px; border-radius: 12px;
font-size: .7rem; font-weight: bold;
}
.t-card h4 { margin: 0 0 .5rem; color: var(--accent); }
.t-card p { margin: 0 0 .75rem; color: var(--text); font-size: .9rem; }
.t-btn {
background: var(--accent); color: white;
border: none; padding: .4rem 1rem;
border-radius: 6px; cursor: pointer; font-size: .9rem;
}
/* Theme 1: Ocean */
#theme-ocean {
--accent: #0077b6;
--card-bg: #e0f4ff;
--card-border: #90cdf4;
--text: #1a3a4a;
--card-shadow: rgba(0,119,182,.12);
}
/* Theme 2: Forest */
#theme-forest {
--accent: #2d6a4f;
--card-bg: #d8f3dc;
--card-border: #95d5b2;
--text: #1b3a2a;
--card-shadow: rgba(45,106,79,.12);
}
/* Layout */
.themes-side-by-side { display: flex; gap: 20px; flex-wrap: wrap; }
.theme-wrap { flex: 1; min-width: 200px; }
.theme-label { font-size: .75rem; color: #888; text-transform: uppercase; letter-spacing: .05em; margin: 0 0 .5rem; }
Bình luận