主题切换最容易做成「每个组件写两套颜色」。更稳妥的做法是:组件只消费语义化变量,真正换肤只改一处 token。
先定义语义 token
在 :root 里放「含义」而不是「色号名字」:
:root {
--bg: #f5f7f6;
--text: #1a2e28;
--accent: #1f7a65;
--border: #c9ddd4;
}
[data-theme="dark"] {
--bg: #121a18;
--text: #e8f0ed;
--accent: #5cbc9f;
--border: #2a3d36;
}
组件样式写成:
body {
background: var(--bg);
color: var(--text);
}
.button-primary {
background: var(--accent);
}
这样暗色模式几乎不用再改组件文件。
用一行 JS 切换
const root = document.documentElement;
const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
页面加载时读回偏好,避免闪一下:
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.setAttribute('data-theme', saved);
若站点完全静态、不想依赖本地存储,也可以只切 data-theme,刷新后回到默认即可。
几个容易踩的坑
- 不要在组件里硬编码十六进制,否则换肤永远漏网。
- 阴影、描边也要进 token,暗色下纯黑阴影往往发脏。
- 图片与插画可能需要单独处理;纯色 UI 用变量就够了。
- 可用
color-scheme提示浏览器原生控件(滚动条、表单)跟随主题。
进阶:系统偏好
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #121a18;
--text: #e8f0ed;
}
}
用户手动选择优先,没有选择时跟随系统,体验更自然。
小结
CSS 变量做主题的核心是「一层语义 token + 根节点切换」。依赖少、改动集中,特别适合个人站和中小型项目。等业务真的复杂到多品牌、多密度时,再考虑设计系统也不迟。