最稳方案是用hsl()+var()配合@media(prefers-color-scheme:dark),但必须完整重写所有HSL变量(--bg-h、--bg-s、--bg-l等),否则Safari静默失效且对比度崩坏;OLED屏明度宜控4%–8%,文字色需同步验证WCAG对比度;Safari旧版对hsl()内var()解析苛刻,禁空格与引号;用户手动切换需JS注入变量并设class,所有var()须带回退值。

直接用 hsl() + var() 配合 @media (prefers-color-scheme: dark) 是最稳的方案,但必须重写全部 HSL 分量(--bg-h、--bg-s、--bg-l 等),否则 Safari 会静默失效,对比度也会崩坏。
为什么不能只改 --bg-l?
媒体查询是“覆盖”,不是“合并”。如果只在 @media 块里写 --bg-l: 6%,而 --bg-h 和 --bg-s 还沿用浅色值,结果可能是 hsl(210, 70%, 6%) —— 高饱和、极暗的蓝灰,文字几乎不可读。
- 深色模式下所有要用到的 HSL 变量(
--bg-h、--bg-s、--bg-l、--text-h、--text-s、--text-l)必须在@media内完整声明 - OLED 屏对明度极敏感:
--bg-l: 4%和--bg-l: 8%视觉差异微弱,但前者更易引发眼疲劳;推荐深色背景明度控制在 4%–8% 区间 - 文字颜色不能只调
--text-l:需同步验证 WCAG 对比度。例如hsl(210, 10%, 20%)在hsl(210, 0%, 6%)背景上约 15:1,安全;若背景是hsl(0, 0%, 2%),同一文字只剩 7:1,必须把文字明度提到 25% 以上
hsl(var(--h),var(--s),var(--l)) 的单位与空格陷阱
旧版 Safari(尤其是 16.4 以下)对 hsl() 内部的 var() 解析极其苛刻,错一处就整条声明静默失效 —— 不报错,也不渲染。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
-
--bg-s: 70%;✅,--bg-s: "70%";❌(引号让变量变成字符串,hsl()拒绝解析) -
hsl(var(--bg-h),var(--bg-s),var(--bg-l))✅(无空格最稳),hsl(var(--bg-h), var(--bg-s), var(--bg-l))❌(Safari 16.4 以下直接跳过整条规则) - 所有百分比值必须带
%单位,且不能加空格或引号;数字类如色相(--bg-h: 210)不带单位
用户手动切换时如何绕过 prefers-color-scheme 限制
点“深色模式”按钮时,系统偏好仍是浅色,@media 不触发,纯 CSS 无法响应。必须靠 JS 主动注入变量,并用 class 标记状态,否则 SSR 渲染和客户端样式脱节,出现 FOUC。
立即学习“前端免费学习笔记(深入)”;
- 首次加载读取
window.matchMedia('(prefers-color-scheme: dark)').matches,存入localStorage;后续一切以该值为准,而非实时查媒体查询 - 切换时执行:
document.documentElement.style.setProperty('--bg-l', '6%'),同时给<html>加class="user-dark" - 每个
var()都必须带回退值,例如color: var(--text-primary, #222),防止变量未定义时彻底失色
真正难的不是“怎么切”,而是变量作用域、单位空格、OLED 明度容忍度、WCAG 对比度验证这四点同时对齐 —— 少一个,用户就在暗色模式下眯着眼找文字。

















