不能直接用hsla()写死颜色,因50%明度在暗背景下刺眼且无法动态调整;必须拆解HSL为独立变量,在:root定义默认值并用@media覆盖,确保全局响应与精准适配。

CSS变量 + HSL 是目前最可控、最易维护的深色模式实现方式,但必须把 HSL 的 h/s/l 三值拆成独立变量,并在 :root 中定义,默认 + @media 覆盖两套值,否则亮度(l%)无法精准适配暗背景。
为什么不能直接用 hsla() 写死颜色?
直接写 hsla(200, 100%, 50%, 0.9) 看似语义清晰,但深色模式下 l% = 50% 在 #121212 背景上会刺眼、对比度过高;更关键的是——它无法被 JS 动态调整。一旦用户手动切换主题,你没法只改 l%,还得重写整个 hsla 字符串,维护爆炸。
- 所有颜色必须走变量引用:
color: var(--text-primary),而不是硬编码hsla(...) - HSL 各通道要拆开:定义
--text-h、--text-s、--text-l、--text-a四个变量,方便单独调控 - 深色模式不是“调暗”,而是重设感知亮度:浅色模式文字 l% 常为 20%–30%,深色模式应降到 15%–25%,同时 s% 往往同步压低 10%–20%
:root 必须声明两套 HSL 变量,且顺序不能错
CSS 变量靠继承和层叠生效,:root 是唯一能保证全局可读的锚点。媒体查询里的覆盖必须写在默认声明之后,否则浏览器解析时找不到初始值,var(--text-l) 会 fallback 到未定义状态,导致颜色失效。
- 先写默认(浅色)值:
:root { --text-h: 200; --text-s: 40%; --text-l: 25%; --text-a: 0.9; } - 再用
@media (prefers-color-scheme: dark)覆盖::root { --text-l: 18%; --text-s: 35%; --text-a: 0.95; } - 别漏掉
html { color-scheme: light dark; },否则<input>、<select></select>等原生控件不随主题变色
组件内用 calc() 组合 HSL,别拼字符串
CSS 不支持字符串拼接,所以不能用 hsla(var(--text-h), var(--text-s), var(--text-l), var(--text-a))——这会报语法错误。正确做法是用 hsl() 函数直接引用数值变量,并靠 calc() 微调。
立即学习“前端免费学习笔记(深入)”;
- 合法写法:
color: hsl(var(--text-h), var(--text-s), calc(var(--text-l) - 2%)); - 禁用态文字可进一步降亮:
color: hsl(var(--text-h), var(--text-s), calc(var(--text-l) * 0.6)); - 避免在
hsl()里混用单位:var(--text-l)是带 % 的,calc(20% - 2%)合法,calc(20 - 2%)非法
手动切换时,.dark 类必须和 :root 变量解耦
用户点击按钮切深色,不能靠 JS 改 document.documentElement.style.setProperty('--text-l', '18%'),这会覆盖媒体查询逻辑,导致系统偏好变更后失效。真正轻量的做法是只操作 class,并让 CSS 规则接管变量赋值。
- 定义
:root.dark { --text-l: 18%; --text-s: 35%; }—— 注意是:root.dark,不是.dark - JS 只做:
document.documentElement.classList.toggle('dark')+localStorage.setItem('theme', 'dark') - 初始化必须早于首屏渲染:放在
<head>内的<script>里,读localStorage.getItem('theme')后立即设置 class,否则闪屏
最容易被忽略的是:所有衍生色(悬停、禁用、边框、阴影)都得走同一套 HSL 变量体系,漏一个就会在深色下露馅——比如 box-shadow 还用着 rgba(0,0,0,0.1),在暗背景上就变成糊成一片的灰影。HSL 的价值不在“写起来多酷”,而在“改一个 l%,整站所有用到它的颜色自动响应”。


















