CSS Custom Properties (biến CSS)

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

Kết quả
<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

Kết quả
<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