你以为主题色只能靠两套变量加媒体查询?今天 CSS 用一个函数把它彻底原生化了
写过带主题色的前端项目的人都踩过这个坑——每个颜色变量都要写两遍,一遍在 :root,一遍在 @media (prefers-color-scheme: dark) 里。新增一个颜色要改两个地方,忘了改就是亮色在暗色背景上翻车。
今天 Firefox 155 正式支持了 light-dark(),加上 color-scheme: light dark 这一行,浏览器自己在系统主题切换时取对应颜色,所有变量只需要写一次。
一句话解释 light-dark() 是什么
light-dark(color1, color2) 接受两个颜色值:第一个给亮色模式用,第二个给暗色模式用。浏览器在渲染时根据当前 color-scheme 自动选一个,不需要任何媒体查询。
:root {
color-scheme: light dark; /* 这行必须写,否则 light-dark() 静默失效 */
--bg: light-dark(#fafafa, #0b0b0d);
--text: light-dark(#1a1a1a, #f4f4f5);
--accent: light-dark(#1a73e8, #8ab4f8);
}
body {
background: var(--bg);
color: var(--text);
}
就这么简单。:root 里的 color-scheme: light dark 是契约,没有它浏览器不知道这个页面支持两种模式,light-dark() 会一直返回第一个值而不出任何报错。
它把三个老写法彻底送走了
旧写法一:变量写两遍
/* 以前:每个 token 两份 */
:root {
--color-bg: #fafafa;
--color-text: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0b0b0d;
--color-text: #f4f4f5;
}
}
/* 现在:每个 token 一份 */
:root {
color-scheme: light dark;
--color-bg: light-dark(#fafafa, #0b0b0d);
--color-text: light-dark(#1a1a1a, #f4f4f5);
}
旧写法二:JS 主题切换覆盖变量
// 以前:JS 读写 data 属性 + CSS 选择器优先级覆盖
document.documentElement.setAttribute("data-theme", "dark");
/* 现在:浏览器原生响应,不需要 JS */
:root[data-theme="dark"] {
color-scheme: dark; /* 强制暗色,light-dark() 自动取第二个值 */
}
手动主题切换只需要在 html data-theme=”dark” 时加一行 color-scheme: dark,light-dark() 自动跟随,不需要覆盖任何变量。
旧写法三:每个组件文件底部的媒体查询
/* 以前:组件文件底部必有一块深色样式 */
.card { background: white; color: black; }
@media (prefers-color-scheme: dark) {
.card { background: #1a1a1a; color: white; }
}
/* 现在:组件里直接写,一行搞定 */
.card { background: light-dark(white, #1a1a1a); color: light-dark(black, white); }
没有媒体查询块,没有选择器重复,只有正确的颜色。
浏览器支持现状
- Chrome 123+(2024 年 3 月)
- Firefox 120+(2024 年 8 月,Firefox 155 即 2026 年 9 月完全稳定)
- Safari 17.5+(2024 年 6 月)
- 全球覆盖率:约 89.86%
- Baseline 2024,已进入无需 polyfill 区间
Firefox 155 还同步支持了 alpha() 函数(修改颜色透明度不改变其他通道)和 progress() 函数(计算区间比例),三者配合可以做出完整的动态主题系统,全部纯 CSS。
三个必须注意的坑
坑一:color-scheme 忘了写
light-dark() 静默失效,不报错,不出 DevTools 警告。如果暗色模式不生效,第一反应查 color-scheme 是否包含 dark。
坑二:在不支持的浏览器里会怎样
不支持的浏览器直接忽略函数,用 var(–color-bg) 的初始值(通常就是第一个参数的值)。只要 :root 的 CSS 变量有亮色默认值,降级体验是永远显示亮色而非页面崩溃。可以加一行 @supports 做渐进增强:
:root {
color-scheme: light dark;
--bg: #fafafa; /* 不支持的浏览器兜底 */
}
@supports (color: light-dark(white, black)) {
:root { --bg: light-dark(#fafafa, #0b0b0d); }
}
坑三:不能和 prefers-color-scheme 混用
两者同时存在时,color-scheme 控制 light-dark() 的取值,@media (prefers-color-scheme: dark) 里的样式会覆盖 light-dark() 的结果。项目中选一种方式就好,推荐 light-dark() + color-scheme 的组合更简洁。
迁移路径:先从设计 token 开始
第一步,在 :root 加 color-scheme: light dark,只加这一行,看暗色模式是否自动生效。
第二步,把设计 token 变量从两处改成一处的 light-dark() 写法,这步最安全,因为只是把原有变量名聚合起来,不改变选择器结构。
第三步,组件里用 light-dark() 替换硬编码颜色值,这步改动最大,建议按组件逐个改,配合 prefers-color-scheme: dark DevTools 模拟验证。
迁移完成后,整个主题切换不再需要 JS,不再需要媒体查询,不再需要手动覆盖变量——系统主题切换、用户手动切换、浏览器强制色彩模式全部由 light-dark() 自动响应。
下一步
打开 DevTools → 模拟暗色模式,看你的页面暗色变量是否已经在生效。如果还没生效,加一行 color-scheme: light dark,很多坏掉的暗色模式可能就这么修好了。
评论区
登录后可评论。