组件专属的浅色/深色主题

Web 功能: color-scheme light-dark()

虽然 color-scheme 属性通常在根元素上设置,但也可以在单个元素上设置,以强制它们使用与页面其余部分不同的配色方案。 这对于必须始终以特定配色方案(例如始终在深色或浅色模式下)查看的组件非常有用。

示例使用场景包括:

  • 出于美观原因,即使在浅色模式页面上也通常采用深色模式的元素,例如代码块、媒体播放器、照片库
  • 即使页面的其余部分处于深色模式,包含专为浅色背景设计的媒体(例如图片、视频、插图、打印预览)的区域也可以设置为浅色模式。
  • 配色方案由用户级设置控制的元素,例如组件预览
  • 不支持浅色和深色模式的嵌入内容
  • 设计工具、地图、可视化、游戏等

何时更改颜色,何时强制使用 color-scheme?

并非每个在浅色模式下使用深色背景上的浅色文字或在深色模式下使用浅色背景上的深色文字的元素都需要不同的 color-scheme。 例如,在浅色模式下,主要按钮可能会呈现为蓝色,并带有白色文字,但这并不保证会使用 color-scheme: dark。

根据经验,使用不同 color-scheme 的元素通常是建立其自身视觉上下文的复杂表面,而不是简单的浅层容器。

在考虑对某个元素使用不同的 color-scheme 时,请问自己:

  • 未以其他方式自定义的内置浏览器界面(例如表单控件、滚动条等)是否应使用该配色方案或适应网页的配色方案?-> 如果是前者,请勿使用 color-scheme。
  • light-dark() 颜色应该像页面其余部分一样解析,还是根据替换值解析?-> 如果是前者,请勿使用 color-scheme。
  • 后代是否应位于该 color-scheme 中?否则,请勿使用 color-scheme。

基本实现

组件专用替换通常(但并非绝对)用于也通过全局 color-scheme 支持多种配色方案的页面。 如需了解如何正确实现全页深色模式,请参阅深色模式。

设置好页面范围的 color-scheme 并且您正在使用对其敏感的颜色令牌(例如通过 light-dark())后,您只需在特定组件上设置 color-scheme 即可替换该子树的颜色模式:

pre, code, .dark {
  color-scheme: dark;
}

请注意,无论如何,某些浏览器都会自动使组件适应不同的配色方案。 如需在所有情况下都强制使用指定的配色方案,请使用 only,即 color-scheme: only dark;,而不是 color-scheme: dark;。

调整非颜色值

light-dark() 目前仅适用于颜色。

最佳做法

  • 强制性要求:请勿在没有背景的元素上设置 color-scheme,因为这可能会混合不同配色方案中的背景和文本颜色对,导致文本无法阅读。
  • 可选:虽然重复使用与页面级深色模式相同的颜色对更容易,但我们可以为这些组件定义不同的颜色对。例如,我们可能希望在浅色模式页面中使用的深色模式组件比在整体处于深色模式的页面中使用的相同深色模式组件稍微浅一些。

需要注意的已知问题

重要注意事项:light-dark() 颜色的继承

light-dark() 会在计算值时解析。 这意味着,任何设置为 light-dark() 颜色的继承 <color> 属性都只会将两种颜色中的一种传递给其后代,而不会传递 light-dark() 表达式本身。

其中包括:

  • 继承的内置颜色属性,例如 color、accent-color、fill、stroke、text-shadow、caret-color
  • 任何已注册且具有 syntax: <color> 和 inherits: true 的可继承自定义属性
  • 任何其他 <color> 属性设置为 inherit

也就是说,您应该:

  • 不得将用于保存设计令牌(例如 --surface-color)的自定义属性注册为 <color>。令牌需要保持其 light-dark() 表达式处于活跃状态,以便后代可以在不同的 color-scheme 下重新解析它们。
  • 在元素上设置 color-scheme 时,请重新指定可能已设置为 light-dark() 值的任何继承 <color> 属性(直接或通过设计令牌),即使该属性设置为相同的设计令牌也是如此。
  • 不在具有 color-scheme 替换项的元素上对 <color> 属性使用 inherit(可以在其后代上使用)。
  • 务必在相反的情况下使用已注册的 <color> 属性:当您有意要对祖先元素的已解析颜色进行快照,并防止其在后代元素的 color-scheme 下重新解析时。例如,捕获页面背景以在其他地方使用。
  • 如果您需要为颜色添加动画效果,请在要添加动画效果的元素上使用单独的 @property 注册 <color> 属性(颜色插值需要注册),这不是设计令牌,而是每个元素的动画目标,因此不会与上述规则冲突。

示例:

:root {
    --accent-color: light-dark(blue, skyblue);
    --surface-color: light-dark(white, #222);
    --text-color: light-dark(#111, white);

    color-scheme: light dark;
    accent-color: var(--accent-color);
    color: var(--text-color);
}

body {
    /* --surface-color dynamically switches despite being inherited because --surface-color is not registered */
    background: var(--surface-color);
}

pre, code {
    color-scheme: dark;
    background: var(--surface-color);

    /* Without this, accent-color would be blue, not skyblue! */
    accent-color: var(--accent-color);

    /* Without this, text-color would be #111, not white! */
    color: var(--text-color);
}

使用 color-scheme 时需要注意的问题

  • Chrome 和 Firefox 会遵循 iframe 的 color-scheme:它们会以正确的配色方案呈现嵌入式网页,并调整嵌入式网页的 prefers-color-scheme 媒体查询,以反映嵌入上下文的 color-scheme。Safari 则不会,即使在 iframe 内,也会将 prefers-color-scheme 解析为系统设置。
    • 如果您同时控制父级和 iframe:请在 iframe 构建时通过网址参数 (?theme=dark) 或通过 postMessage()(还可让您对运行时更改做出响应)将父级的配色方案明确传递给 iframe。在 iframe 中,根据该信号在 <html> 上设置一个类(和/或在 :root 上设置 color-scheme),而不是依赖于 prefers-color-scheme。
    • 如果您只能控制嵌入式网页:在 Safari 中,无法通过 iframe 可靠地检测嵌入上下文的 color-scheme。在嵌入 API 上公开明确的主题参数(例如查询字符串或 postMessage 协议),并为嵌入者提供相关文档。

后备策略

color-scheme 的后备方案和浏览器支持

color-scheme 属性是渐进增强。不支持此属性的浏览器会忽略它,并使用其默认的浅色模式界面。

如需在旧版浏览器中适应用户的偏好设置,请使用 prefers-color-scheme 媒体查询,在用户偏好深色模式时提供不同的颜色。

  • 请使用媒体查询在 :root 或 html 上切换自定义属性
  • 避免在单个组件上使用媒体查询,除非该组件需要非常特定的深色模式自定义(颜色除外)。
:root {
  /* Define brand colors for each mode */
  --color-brand-light: #0056b3;
  --color-brand-dark: #00e5ff;
  --color-brand: var(--color-brand-light);

  /* MANDATORY: Fallback for browsers without light-dark support */
  @media (prefers-color-scheme: dark) {
    --color-brand: var(--color-brand-dark);
  }

  /* Ignored in older browsers */
  color-scheme: light dark;
}

button.primary {
    background-color: var(--color-brand);
}

light-dark() 的回退和浏览器支持

对于支持 color-scheme 但尚不支持 light-dark() 的浏览器,应首先将颜色的浅色和深色版本定义为自定义属性,并使用 prefers-color-scheme 媒体查询为相应模式设置颜色,如以下示例所示:

:root {
  /* Define browser UI accent color for each mode */
  --brand-accent-light: #0056b3;
  --brand-accent-dark: #00e5ff;
  --accent-color: var(--brand-accent-light);

  /* MANDATORY: Fallback for browsers without light-dark support */
  @media (prefers-color-scheme: dark) {
    --accent-color: var(--brand-accent-dark);
  }

  /* OPTIONAL: use light-dark() for more control of built-in UI colors */
  @supports (color: light-dark(white, black)) {
    --accent-color: light-dark(var(--brand-accent-light), var(--brand-accent-dark));
  }

  /* MANDATORY: Automatically adapt native UI to user system preferences */
  color-scheme: light dark;

  /* Example inherited color property */
  accent-color: var(--accent-color);
}

pre, code {
  color-scheme: dark;

  /* **Mandatory**: any inherited color properties must be set again, even if to the same design tokens */
  accent-color: var(--accent-color);
}