CSS 變數 Custom Properties 主題切換教學:從零打造亮色暗色模式
說真的,在認識 CSS Custom Properties 之前,我一直以為 Sass 變數就是唯一的解法。直到某天開始認真研究瀏覽器原生的 CSS 變數,才發現這東西根本就是主題切換的神器。今天這篇教學會從基礎概念開始,帶你一步步建立一個完整的亮色/暗色模式切換器。
什麼是 CSS Custom Properties?
CSS Custom Properties 是 CSS 原生支援的變數系統,語法上用兩個破折號開頭,像這樣:--my-color: #3498db;,然後用 var(--my-color) 來取用。
跟 Sass 或 Less 的預處理器變數最大的差別在於:CSS 變數是在執行時期(runtime)運算的,而預處理器變數在編譯階段就已經被替換成靜態值了。因為 CSS 變數是即時的,你可以用 JavaScript 動態修改它們,也可以讓它們根據 :root 或某個特定的父元素繼承不同的值。這才是主題切換的真正關鍵所在。
/* Sass 變數 - 編譯後消失 */
$primary-color: #3498db;
/* CSS Custom Property - 瀏覽器執行時仍然存在 */
:root {
--primary-color: #3498db;
}
.button {
background: var(--primary-color);
}
2026 年的 @property 規則
@property 讓你可以明確定義一個 CSS 自訂屬性的型別、初始值跟是否可繼承。定義了型別之後,CSS transition 和 animation 就可以對這些變數做動畫了!主題切換時顏色漸漸過渡的流暢感,完全不需要任何 JavaScript 動畫邏輯。
@property --theme-hue {
syntax: '<number>';
inherits: true;
initial-value: 220;
}
如果你對 CSS 動畫有更多興趣,推薦去看看 CSS 捲動驅動動畫 的深入教學。
手把手建立主題切換器
步驟一:定義設計 Token
在 :root 裡定義所有的設計 Token,分成原始值層跟語意層:
:root, [data-theme="light"] {
--color-background: #f9fafb;
--color-surface: #ffffff;
--color-text-primary: #111827;
--color-text-secondary: #6b7280;
--color-border: #e5e7eb;
--color-accent: #3b82f6;
--color-accent-hover: #2563eb;
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
}
[data-theme="dark"] {
--color-background: #0f172a;
--color-surface: #1e293b;
--color-text-primary: #f1f5f9;
--color-text-secondary: #94a3b8;
--color-border: #334155;
--color-accent: #60a5fa;
--color-accent-hover: #93c5fd;
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
}
步驟二:在元件中使用語意 Token
元件只使用語意層的變數。切換主題時,只需要修改語意層的變數值,所有元件就會自動更新。
body {
background-color: var(--color-background);
color: var(--color-text-primary);
transition: background-color 0.2s ease, color 0.2s ease;
}
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
box-shadow: var(--shadow-md);
}
.button-primary {
background: var(--color-accent);
color: white;
border-radius: 0.375rem;
}
.button-primary:hover {
background: var(--color-accent-hover);
}
步驟三:JavaScript 切換邏輯
const toggle = document.getElementById('theme-toggle');
const html = document.documentElement;
const getPreferredTheme = () => {
const saved = localStorage.getItem('theme');
if (saved) return saved;
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light';
};
const applyTheme = (theme) => {
html.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
};
applyTheme(getPreferredTheme());
toggle.addEventListener('click', () => {
const current = html.getAttribute('data-theme');
applyTheme(current === 'dark' ? 'light' : 'dark');
});
// 監聽系統主題變化
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
applyTheme(e.matches ? 'dark' : 'light');
}
});
設計系統中的最佳實踐
在實際專案中我踩過不少坑,分享幾個讓 CSS 變數架構保持健康的原則。命名慣例要一致,推薦 --[category]-[property]-[variant] 格式。善用 var() 的第二個參數作為 fallback 值。適當縮小變數的作用域,不是所有變數都要放在 :root。
搭配 CSS Container Queries 使用,可以讓元件根據容器大小自適應,同時維持主題一致性。
CSS 變數的效能優勢
傳統做法(切換 class 或替換 stylesheet)會觸發大範圍的重繪。但 CSS 變數方式,瀏覽器只更新受影響的屬性值。根據實際測試,上百個元件的頁面切換主題通常在 16ms 以內完成。
如果你的專案是使用 Tailwind CSS,也可以搭配 Tailwind CSS 暗色模式 的實作方式。
總結
CSS Custom Properties 搭配 @property 規則、三層式 Token 架構,以及尊重系統偏好的 JavaScript,你可以建立一個既優雅又高效的主題系統。如果你還在用純 Sass 變數做主題切換,強烈建議試試 CSS 原生解法。
你可能也喜歡
探索其他領域的精選好文