实现浅色与深色配色方案

最后更新: 2023-12-05 18:27:09 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

配色方案实现需要三层:

  1. 访问者未作站点特定选择时使用的系统偏好。
  2. 可以覆盖系统偏好的显式站点偏好。
  3. 让组件独立于字面浅色和 dark values.

声明语义颜色 #

按角色而不是外观命名变量:

css
:root {
  color-scheme: light dark;
  --surface: #ffffff;
  --surface-raised: #f5f6f8;
  --text: #17191c;
  --text-muted: #5d6470;
  --border: #d7dbe0;
  --accent: #1659b7;
}

body {
  color: var(--text);
  background: var(--surface);
}

color-scheme 告知用户代理页面支持哪些方案,从而允许 form controls, scrollbars, and system colors to match. It does not define the site’s custom palette.

An equivalent early hint lets the browser choose a suitable canvas color before the stylesheet arrives:

html
<meta name="color-scheme" content="light dark">

根据系统偏好应用深色调色板:

css
@media (prefers-color-scheme: dark) {
  :root {
    --surface: #17191c;
    --surface-raised: #22262b;
    --text: #eef0f2;
    --text-muted: #b5bbc3;
    --border: #3a4048;
    --accent: #8ab4f8;
  }
}

The obsolete no-preference value should not be used.

定义显式覆盖优先级 #

将访问者的显式选择存储为 light、dark 或 system。 An attribute on the root element can override the media query:

css
:root[data-theme="light"] {
  color-scheme: light;
  --surface: #ffffff;
  --surface-raised: #f5f6f8;
  --text: #17191c;
  --text-muted: #5d6470;
  --border: #d7dbe0;
  --accent: #1659b7;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --surface: #17191c;
  --surface-raised: #22262b;
  --text: #eef0f2;
  --text-muted: #b5bbc3;
  --border: #3a4048;
  --accent: #8ab4f8;
}

The JavaScript state transition remains small:

js
function setTheme(theme) {
  const root = document.documentElement;

  if (theme === "system") {
    delete root.dataset.theme;
    localStorage.removeItem("theme");
    return;
  }

  root.dataset.theme = theme;
  localStorage.setItem("theme", theme);
}

Restrict stored values before assigning them because local storage is mutable:

js
const saved = localStorage.getItem("theme");
if (saved === "light" || saved === "dark") {
  document.documentElement.dataset.theme = saved;
}

Run the initialization in the document head or an inline startup script before the body renders. Late application can flash the wrong palette. Sites with a strict content security policy can use a nonce or generate the root attribute server-side from a validated cookie.

响应系统变化 #

No listener is required when CSS media queries control system mode. JavaScript needs a listener only when application state or a canvas must react:

js
const query = matchMedia("(prefers-color-scheme: dark)");

query.addEventListener("change", ({ matches }) => {
  if (!document.documentElement.dataset.theme) {
    redrawCanvas(matches ? "dark" : "light");
  }
});

An explicit site preference should not be overwritten by a later operating system change.

为嵌入文档设置主题 #

An inline SVG participates in the parent document’s cascade. An SVG loaded through <img>, and a document loaded through <iframe>, have separate style trees, so parent custom properties do not cross those boundaries.

The color-scheme used for an embedding element can influence the embedded document’s canvas and its prefers-color-scheme result, but it does not inject the parent’s authored palette. For application-controlled themes, use resource variants or a deliberate postMessage protocol for same-application frames. Validate message origins before applying any state.

保持对比度与含义 #

Dark mode is not a mechanical inversion. Verify:

  • Text and interactive-control contrast.
  • Focus indicators in both schemes.
  • Disabled and muted states without relying on opacity alone.
  • Charts and status colors with labels or shapes in addition to hue.
  • Images, shadows, syntax highlighting, and native form controls.

Use semantic variables so a contrast correction changes one role across the site. Test forced-colors mode separately because user-agent colors can replace the authored palette and remove effects such as box shadows:

css
@media (forced-colors: active) {
  .focusable {
    border: 1px solid transparent;
  }
}

Use system colors and non-color cues in this mode. Reserve forced-color-adjust: none for content whose literal colors carry necessary information, such as a color swatch, because it opts the element out of the user’s contrast substitutions.

评论

在 GitHub 上阅读或评论(发表评论需要登录)