暗色模式现在是基础体验。正确做法是用 CSS 变量统一所有颜色,
然后 @media (prefers-color-scheme: dark) 覆盖变量。
手动切换则用 [data-theme="dark"] 选择器。
1. 颜色变量化
:root {
--bg: #ffffff;
--card: #f8f9fa;
--text: #1a1a1a;
--text-soft: #6b7280;
--border: #e5e7eb;
--primary: #2563eb;
--primary-soft: #eff6ff;
}
body {
background: var(--bg);
color: var(--text);
}
.card {
background: var(--card);
border: 1px solid var(--border);
}
任何颜色都引用变量,绝不写死 color: #333。
2. 自动跟随系统(最少代码)
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f172a;
--card: #1e293b;
--text: #f1f5f9;
--text-soft: #94a3b8;
--border: #334155;
--primary: #60a5fa;
--primary-soft: #1e3a8a;
}
}
只这样:暗色模式自动跟系统设置变。
3. 手动切换(覆盖系统设置)
加 [data-theme] 优先级:
:root { --bg: #fff; /* ... */ }
@media (prefers-color-scheme: dark) {
:root { --bg: #0f172a; /* ... */ }
}
[data-theme="dark"] {
--bg: #0f172a; /* ... */
}
[data-theme="light"] {
--bg: #ffffff; /* ... */
}
JS:
function setTheme(theme) { // 'dark' | 'light' | 'system'
if (theme === 'system') {
document.documentElement.removeAttribute('data-theme')
localStorage.removeItem('theme')
} else {
document.documentElement.setAttribute('data-theme', theme)
localStorage.setItem('theme', theme)
}
}
// 启动时还原
const saved = localStorage.getItem('theme')
if (saved) document.documentElement.setAttribute('data-theme', saved)
4. 避免 FOUC(First Of Unstyled Content)
如果上面的代码放在 <script> 模块末尾,会有一闪而过的错误主题。
解决:把"还原主题"提前到 <head> 顶部、inline 脚本:
<head>
<script>
(function() {
var saved = localStorage.getItem('theme')
if (saved) document.documentElement.setAttribute('data-theme', saved)
})()
</script>
<!-- 后面才是 CSS -->
</head>
inline 脚本同步执行,CSS 应用前主题已就位。
5. 监听系统切换
用户用着用着把系统切到暗色——网页要立刻跟上:
const mq = window.matchMedia('(prefers-color-scheme: dark)')
mq.addEventListener('change', e => {
// 只在没设手动主题时跟随
if (!localStorage.getItem('theme')) {
// CSS @media 会自动响应,这里不需要做什么,但如果你有 JS
// 在用 mediaquery 判断当前主题,要刷新
updateChartTheme(e.matches ? 'dark' : 'light')
}
})
6. 图像 / iframe 内容怎么办
普通 <img> 没法跟随暗色。常用方案:
<picture>
<source srcset="logo-dark.svg" media="(prefers-color-scheme: dark)">
<img src="logo-light.svg" alt="Logo">
</picture>
SVG icon 是 CSS color 控制的(用 currentColor):
<svg class="icon" fill="currentColor">...</svg>
.icon { color: var(--text); }
iframe 内容(地图、第三方嵌入)就只能自己看是否支持 ?theme=dark
参数。
7. 配色技巧
不是简单地"颜色取反",几个要点:
- 暗色背景不要纯黑(#000),用 #0d1117 / #0f172a 这种"接近黑"
减少 contrast 疲劳 - 卡片背景比 body 稍亮(如 #161b22)
- 文字 #e6edf3 而不是 #fff,避免高对比刺眼
- 主色 brand color 通常要在暗色下稍微 desaturate + lighten
- shadow 在暗色下基本看不见,可以用 border 替代分隔
GitHub Dark / Tailwind slate 系列是优秀范本,直接借用色值。
8. 给暗色添加缓动
切换时颜色突变很扎眼,加 transition:
:root {
/* ... */
}
body, .card, .button {
transition: background-color .2s, color .2s, border-color .2s;
}
但别把 transition: all 加在所有元素上,会触发不必要的重排。
踩过的坑
- 写死的 SVG 颜色 / inline style 是暗色模式的常见漏网之鱼。grep 整个
代码库#[0-9a-fA-F]{3,6}看哪些没用变量。 - 图表库 / 代码高亮通常自带主题,需要单独切。Pygments / Highlight.js
都有 light + dark theme 包。 - 系统暗色模式但用户手动选浅色——很多人不太会用,记得在 settings 里
加 "跟随系统 / 强制亮色 / 强制暗色" 三选项。 - 别忘了 favicon /
meta[name=theme-color]也支持 prefers-color-scheme:
html <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> <meta name="theme-color" content="#0f172a" media="(prefers-color-scheme: dark)">
登录后参与评论。