深色模式

color-scheme 属性用于指明您的网页支持哪些配色方案(例如浅色或深色)。这会告知浏览器,它可以自动为原生界面元素(例如滚动条、表单控件和默认画布背景)设置主题,以匹配您网站的设计,并有助于在初始加载期间最大限度地减少白色闪烁。

实现

1. 在 HTML 中声明支持的架构

强制性:为帮助防止“无主题内容闪烁”(FOUC),请在 <head> 中放置一个 <meta> 标记,以确保浏览器在开始渲染之前就知道您支持哪些主题。虽然此 <meta> 标记通过尽早设置初始画布颜色来帮助避免 FOUC,但它可能无法在所有浏览器或加载条件下完全消除闪烁。

<!-- MANDATORY: Declare support for both light and dark themes -->
<meta name="color-scheme" content="light dark">

2. 将页面级配色方案应用于 CSS :root 或 html

强制性:将 color-scheme 属性应用于 html 元素或 :root 伪类。浏览器会专门查看根元素,以确定整个视口的主题,包括根滚动条和初始“画布”背景。如果仅应用于 body,这些全局界面表面可能会保持浅色模式,因为 body 无法控制窗口的渲染上下文。

/* MANDATORY: Apply color-scheme to :root or html for viewport-wide theming */
:root {
  /* MANDATORY: Automatically adapt native UI to user system preferences */
  color-scheme: light dark;
}

3. 定义浅色和深色颜色令牌

您可以使用 light-dark() 函数定义自动适应不同 color-scheme 值的颜色令牌。

建议您还将原始颜色值保留在单独的自定义属性中,这样可以更轻松地以不同方式组合它们(并在需要时更轻松地实现后备行为)。

为了更好地控制 accent-color 或 scrollbar-color 等内置界面的颜色,作者可以选择使用自定义属性和/或 light-dark() 函数添加自己的动态颜色。此函数会根据计算出的元素 color-scheme 自动选择正确的颜色,从而避免了冗余的媒体查询,但基本实现不需要此函数。

:root {
  --color-brand-light: oklch(45% 0.23 270);
  --color-brand-dark: oklch(85% 0.15 210);
  --color-brand-text-light: white;
  --color-brand-text-dark: oklch(40% 0.23 270);

  --color-brand: light-dark(var(--color-brand-light), var(--color-brand-dark));
  --color-brand-text: light-dark(var(--color-brand-text-light), var(--color-brand-text-dark));

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

button.primary {
  /* These automatically adapt to color scheme */
  background-color: var(--color-brand);
  color: var(--color-brand-text);
}

可选:有许多系统颜色可供使用,这些颜色也会自动适应所使用的配色方案(以及其他颜色模式,例如强制颜色),例如 canvas、canvastext、accentcolor(检查支持情况)、buttonborder 等。这些颜色通常过于有限,除了需要与某些默认浏览器界面完全匹配或作为后备/默认颜色的极少数情况外,其他情况下用处不大。

可选:根据上下文调整颜色对

即使替换系统默认设置,使用 prefers-color-scheme 媒体查询来定义不同的颜色对也很有用,这些颜色对会考虑页面周围的浏览器和操作系统 Chrome 的颜色(或周围页面的颜色,当页面用作 iframe 时)。

例如,当系统设置为 dark 时,使用稍微暗一点的浅色主题;当系统设置为 light 时,使用对比度更高的深色主题,这样页面就不会在视觉上被周围的界面所压制。

精细的浏览器界面自定义

设置 color-scheme 已经可以使浏览器界面适应所用的配色方案,但这会使用可能无法与网站设计完美契合的操作系统默认设置和/或系统颜色。 现代浏览器为这些功能提供了多个精细的自定义钩子。 在未充分利用现代浏览器提供的自定义钩子的情况下,请勿仅为了自定义原生控件的外观而重新实现它们。

设置强调色

某些浏览器界面(例如选中的复选框或滑块)会使用强调色。 默认情况下,此属性会解析为操作系统设置,但您可以使用 accent-color 属性将其设置为与页面更协调的颜色,例如页面的品牌颜色。

html {
  accent-color: light-dark(var(--color-accent-light), var(--color-accent-dark));
}

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

accent-color 的浏览器呈现方式各不相同。 对于通常绘制在强调色背景上的指示器(例如复选框标记和单选按钮圆点),浏览器可能会采用不同的处理方式:

  • Chromium 和 Firefox 通常会选择对比鲜明的指示器颜色。
  • Safari 可能会在浅色模式下保留白色指示器,或在深色模式下保留黑色指示器,同时修改其背后的强调色。这种调整并不总是能为每种提供的颜色提供足够的对比度,因此请在 Safari 中测试重要的组合,包括浅色和深色配色方案。不要假设 Safari 会自动选择对比度最佳的指示器颜色。

滚动条颜色

您可以将 scrollbar-color 与 light-dark() 结合使用,设置可适应所用配色方案的自定义滚动条颜色。

:root {
  --color-scrollbar-track: light-dark(#eee, #222);
  --color-scrollbar-thumb: light-dark(#999, #666);
  scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track);
}

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

  • 请勿为 scrollbar-color 添加动画或过渡效果。WebKit bug 会导致滚动条在每次 scrollbar-color 发生变化时闪烁。
  • 在 macOS 上,系统会默认忽略 scrollbar-color(标准)和 ::-webkit-scrollbar(旧版)属性,因为 macOS 使用原生“叠加”滚动条。您必须将自定义颜色与 scrollbar-width(例如 thin 或 auto)配对,以强制 macOS 呈现这些颜色。
  • 即使应用了 scrollbar-width,macOS 叠加滚动条默认也会将轨道(边距)呈现为透明。如果设计要求在 macOS 上显示可见的滑轨背景颜色,您必须将 scrollbar-gutter: stable; 应用于可滚动容器,但请注意,该颜色仅在用户将鼠标悬停在滚动条上时才会显示。
  • 即使使用 scrollbar-gutter: stable,轨道在 macOS 上也可能是透明的。滑块不应依赖于滑轨颜色来显示。

进一步自定义

大多数浏览器界面都会公开伪元素,以便完全自定义其外观,例如:

  • ::placeholder
  • ::spelling-error
  • ::grammar-error
  • ::selection
  • ::search-text
  • ::target-text
  • ::file-selector-button

您可以在其中任何一个元素上使用 light-dark() 颜色,以应用与所用配色方案相适应的颜色。

4. 浅色模式和深色模式下的不同图片

对于 CSS 图片,light-dark() 也可用于提供不同的图片值。

重要提示:light-dark() 中的两个值必须属于同一类型。您无法将颜色与图片组合在一起。 如果其中一个必须是图片,请使用 image(<color>) 封装颜色值。

基于 JS 的主题检测

大多数配色方案分支都应在 CSS 中完成,CSS 会自动适应更改。

任何读取 matchMedia("(prefers-color-scheme: dark)").matches 的 JS 还必须处理其 change 事件,因为系统偏好设置可能随时发生变化。

可选:实现配色方案切换开关

如果您想允许用户为此网站手动覆盖系统默认设置,可以实现深色模式切换开关。 大多数网站不需要在网站界面中提供永久性切换开关。 通常,只需默认使用系统配色方案即可,也可以在单独的设置界面中进行设置。 如需了解有关实现深色模式切换开关(永久性或非永久性)的最佳实践,请参阅深色模式切换开关。

可选:组件专用替换

您可以通过为特定元素设置 color-scheme 来替换全局主题。

如需了解详情和注意事项,请参阅组件专用浅色/深色主题。

需要注意的已知问题

使用 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);
}

light-dark() 图片值的后备和浏览器支持

对于不支持对图片使用 light-dark() 的浏览器,请使用 image-set() 作为开关:为每个选项提供一个来自自定义属性的 type(),并将非活跃选项设置为不受支持的 MIME 类型,以便浏览器舍弃该选项。 active 选项的值为空,因此它是一个不带 type() 的普通 image-set() 选项。

在 :root 上定义一次开关。然后,每个使用网站只需要内嵌这两个图片,例如 light-dark()。

:root {
  --light-type: ;
  --dark-type: type("image/do-not-use");

  /* MANDATORY: Fallback for browsers without light-dark() image support */
  @media (prefers-color-scheme: dark) {
    --light-type: type("image/do-not-use");
    --dark-type: ;
  }
}

.hero {
  background-image: image-set(url(hero-light.png) var(--light-type), url(hero-dark.png) var(--dark-type));

  @supports (background-image: light-dark(url("a"), url("b"))) {
    background-image: light-dark(url(hero-light.png), url(hero-dark.png));
  }
}
  • 系统只会下载所选图片。
  • 解决方法仍然有效:image-set(url(a-1x.png) 1x var(--light-type), url(a-2x.png) 2x var(--light-type), url(b-1x.png) 1x var(--dark-type), url(b-2x.png) 2x var(--dark-type))。
  • 请勿将 type("") 用于活动选项。浏览器会将空字符串视为不受支持,并会丢弃每个选项。
  • 与 light-dark() 不同,该开关不遵循在子树上设置的 color-scheme。手动切换开关或设置 color-scheme 的任何子树也必须切换 --light-type 和 --dark-type。

替代方案:每张图片一个自定义属性

当只有少数图片发生变化时,这种方法更简单,但每张图片都需要有自己的媒体查询分支,并且必须手动切换每个分支。

:root {
  --hero-image: url(hero-light.png);

  @media (prefers-color-scheme: dark) {
    --hero-image: url(hero-dark.png);
  }

  @supports (background-image: light-dark(url("a"), url("b"))) {
    --hero-image: light-dark(url(hero-light.png), url(hero-dark.png));
  }
}

.hero {
  background-image: var(--hero-image);
}

image() 的后备和浏览器支持

使用具有单个色标且没有位置的渐变,例如 linear-gradient(blue)。

为了获得更广泛的浏览器支持,您可以为单个颜色停止点明确提供 0 100% 位置,例如 linear-gradient(blue 0 100%)。

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

此功能属于渐进增强,并不总是需要后备。

如果样式很重要,并且用户的基准目标是“基准广泛可用”或更早版本,则您应添加非标准 ::-webkit-scrollbar 伪元素作为后备。

将旧版后备样式封装在 @supports not (scrollbar-color: auto) 代码块中,以防止在原生支持标准属性和旧版 WebKit 选择器的浏览器中发生冲突。

如果您使用自定义属性来定义颜色,这些属性会自动级联到旧版 WebKit 选择器。您无需复制这些内容。

/* Legacy fallback for WebKit/Blink browsers */
@supports not (scrollbar-color: auto) {
  .scroller::-webkit-scrollbar {
    /* Must define base size in WebKit for custom colors to be visual */
    width: 12px;
    height: 12px;
  }

  .scroller::-webkit-scrollbar-thumb {
    background: var(--scrollbar-thumb);
  }

  .scroller::-webkit-scrollbar-track {
    background: var(--scrollbar-track);
  }
}

accent-color 的回退和浏览器支持

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