配色方案实现需要三层:
- 访问者未作站点特定选择时使用的系统偏好。
- 可以覆盖系统偏好的显式站点偏好。
- 让组件独立于字面浅色和 dark values.
声明语义颜色 #
按角色而不是外观命名变量:
: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:
<meta name="color-scheme" content="light dark">根据系统偏好应用深色调色板:
@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:
: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:
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:
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:
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:
@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 上阅读或评论(发表评论需要登录)