深色模式切换开关可让用户为一个网站替换系统配色方案。
通过更新 <meta name="color-scheme"> 来应用替换:light dark 表示“遵循系统偏好设置”,light 或 dark 表示固定明确的配色方案。
颜色 (light-dark())、系统颜色和原生界面都取决于 CSS color-scheme 属性,因此该切换开关通过使该属性与 <meta> 保持同步来发挥作用(请参阅在 CSS 中反映替换)。
是永久性切换开关、设置控件,还是根本没有切换开关?
大多数网站不需要在网站界面(页眉、导航栏、页脚等)中提供永久性配色方案切换开关。 默认情况下,遵循系统偏好设置;如果确实需要手动替换,请将控件放在单独的设置界面中:设置页面、面板或叠加层。
将配色方案固定到操作系统是一种合理但罕见的意图,而罕见的意图应该隐藏在渐进式披露机制之后,而不是放在始终可见的控件中。
持久切换开关主要出现在面向开发者或与开发者相关的网站(例如面向设计师的网站)上,在这些网站上,切换配色方案的任务比普通 Web 用户要频繁得多。 请勿仅仅因为开发者网站上常见就添加此类控件:开发者对此类控件的兴趣并不代表典型用户的兴趣。
实现
首先,按照深色模式中的所有步骤操作,以支持这两种配色方案,并默认使用系统偏好设置。
数据模型
深色模式切换开关的数据模型通常包含三种状态:
- 系统(无存储值):界面遵循系统配色方案。理想情况下,这应该是默认设置。
- 浅色(存储为
"light"):无论系统配色方案如何,界面都使用浅色主题。 - 深色(存储为
"dark"):无论系统配色方案如何,界面都使用深色主题。
此状态通常存储在 localStorage 中,并反映在文档标头中的 <meta name="color-scheme"> 元素中。
在 CSS 中反映替换
<meta> 只是一个呈现提示:作者 CSS 中的任何 color-scheme 声明都会替换它。
由于深色模式会在 :root 上设置 color-scheme: light dark,因此仅更新 <meta> 是无效操作,CSS 优先。
将 <meta> 映射回属性,以便级联遵循该属性:
:root {
color-scheme: light dark;
&:has(> head > meta[name="color-scheme"][content="dark"]) {
color-scheme: dark;
}
&:has(> head > meta[name="color-scheme"][content="light"]) {
color-scheme: light;
}
}
请勿将 color-scheme: light 或 color-scheme: dark 硬编码为根默认值;基本声明必须保持为 light dark。
CSS 不得依赖于 JS:如果 JS 永远不运行,<meta> 会保持 light dark,网站会遵循系统偏好设置,不会出现任何问题。
持久性和 FOUC 防范
将替换设置保留在 localStorage 中;在恢复为系统默认设置时移除该条目。
为避免出现错误的配色方案,请在紧跟 <meta> 元素之后的内嵌脚本(而非 type=module,也非 defer)中应用存储的值:
<meta name="color-scheme" content="light dark">
<script>
document.querySelector('meta[name="color-scheme"]').content = localStorage.getItem("color-scheme") ?? "light dark";
</script>
- 此内嵌脚本仅用于防止 FOUC。尽量保持其体积小巧;用于处理配色方案切换的额外 JS 可以稍后加载。
- 在极少数情况下,
localStorage访问可能会抛出错误(例如,网站数据被屏蔽),但由于此脚本不执行其他操作,因此唯一的副作用是控制台错误。如果这很重要,请用try .. catch封装。确保主题仍应用于页面,即使无法持久保存。 - 替换项可能会从另一个标签页发生更改:请处理
window的storage事件以保持同步。 - 当系统偏好设置更改为与存储的值一致时,请勿使用
matchMedia()移除存储的值。 许多用户的操作系统会根据一天中的时间自动切换配色方案;如果每当两者恰好一致时就移除存储的值,则根本无法固定配色方案。
针对 HTML 和非颜色值的分支
颜色无需额外操作:一旦 <meta> 映射到 color-scheme(如上所示),light-dark() 就会遵循替换。
本部分介绍如何调整其他值(例如 font-weight、媒体来源等)。
如果网页只是遵循系统偏好设置,则可以使用 (prefers-color-scheme: dark) 进行分支,包括在 <picture> 或 <video> 来源中。替换配色方案会增加额外的复杂性:系统偏好设置不再是可靠来源,对于仅接受媒体查询的 API,没有直接的替代方案。
请勿完全依赖于 JS 应用的类(例如 .dark)进行分支,因为如果 JS 未加载且操作系统默认设置为深色,这些类将不正确。
您可以将媒体查询与 :root:has(> head > meta[name="color-scheme"][content="dark"])(或 light)选择器结合使用,以创建分支。
如需详细了解如何巧妙地实现这一点,请参阅选择器和 @ 规则组合。
控件本身的选项也是非颜色分支:在标记中包含两种状态(图标以及视觉上隐藏的操作文本,例如“切换到深色主题”),并通过这些选择器隐藏非活动状态。
display: none 还会从 AT 中移除多余的选项,因此当系统偏好设置发生变化时,控件无需 JS 即可保持最新状态。
对于 HTML 内嵌媒体,目前唯一的办法是将两个版本作为单独的元素包含在内,并适当地切换可见性。
使用 display: none 隐藏 <source> 元素不起作用。
界面
两种还是三种状态?
在所有情况下,界面都必须支持将配色方案还原或设置为系统配色方案。
在空间受限的切换开关中,最好显示两种状态,例如网站 Chrome(标题、导航、页脚等)中的常驻切换开关,这些位置空间有限,且调整设置通常与用户目标无关。
在单独的设置界面(页面、面板或叠加层)中,配色方案控件属于不同的场景,可以明确显示所有三种状态(“浅色”“深色”“系统”):在这种情况下,用户已经对未来的行为做出了深思熟虑的决定,并且有空间来解释这些选项。
如果网站实现了情境感知方案(例如,当操作系统处于深色模式时,网站会采用较暗的浅色模式),那么也需要提供三种明确的状态,因为此时“浅色”和“系统(当前为浅色)”确实存在差异。
双态切换开关
使用此类控件时,往往是为了临时调整舒适度(“现在太亮了”),而不是为了确保当前状态长期保持不变。 因此,对于双态切换开关,只需显示三种设置中的两种即可:系统设置(以便还原设置)和当前与系统设置相反的设置(存储为其当前字面量值)。
仅有的两个状态应为:
- 系统默认 - 无存储值,
<meta>内容light dark。显示为其当前解析的值(例如,浅色主题时显示太阳图标)。 - 替换 - 按字面存储为
light或dark。
本质上,它是一个三态控件(light dark、light、dark),其中与当前系统偏好设置匹配的显式状态无法访问。
当用户切换以下设置时:
- 目标配色方案 = 与当前呈现的配色方案相反(存储的值 [如果有],否则为系统偏好设置)。用户意图是“选择与我当前看到的相反的选项”,而不是“选择与系统默认设置相反的选项”。
- 如果目标语言与当前系统偏好设置不同,请按字面意思存储。
- 如果目标值与当前系统偏好设置一致,则表示用户正在撤消其调整:移除存储的值。请勿存储该值,否则会隐式固定该方案,使其无法适应未来的系统更改。
- 将
<meta>内容设置为存储的值,如果没有,则设置为light dark。
重要提示:必须仅在存储时检查差异,绝不能追溯检查:如果系统偏好设置后来更改为与存储的值匹配,则必须保留该存储的值。 许多用户的操作系统会根据一天中的时间自动切换配色方案;如果每当两者恰好一致时就移除存储的值,则根本无法固定配色方案。
示例场景,从操作系统设置为浅色开始:
- 用户切换。深色与系统偏好设置不同,因此系统会存储
dark;网站会变为深色。 - 操作系统设置会更改为深色。网站保持深色模式(存储的值现在与系统偏好设置一致,但仍会保留)。
- 操作系统设置会改回浅色。网站保持深色模式。
- 用户切换。目标(浅色)与系统偏好设置一致,因此存储的值会被移除,网站会再次遵循系统设置。
三态切换开关
当有更多空间,且用户已经有设置长期偏好设置的想法时,三态控件可以提供更清晰的信息。 在这种情况下,数据模型会清晰地映射到三个界面选项:系统、浅色和深色。
为了减轻认知负荷,“系统”选项还应显示当前的系统设置,例如:
- 图标,例如系统图标右下角的太阳图标
- 文本,例如“系统(当前为浅色)”