Theming 是一个主题系统构建技能,用于在浅色、深色、强制颜色(forced-colors)或用户保存的主题偏好之间,保持组件语义角色的一致映射,同时避免对比度或主题闪烁(no-flash)回归。
用途 主题系统让组件角色保持稳定,而模式只重新映射值。组件引用语义令牌,例如 --color-bg、--color-text 和 --color-focus;在声称可访问性之前,必须测量每一种模式。深色不是浅色的反转;作者定义的高对比度也不是强制颜色。
主要能力
- 决策顺序:首先定义语义令牌,包括 --color-bg、--color-surface-1/2(若需要层级则加 -3)、--color-text、--color-text-muted、--color-border、--color-primary、--color-primary-hover、--color-primary-ink、--color-accent、--color-accent-ink、--color-success、--color-error、--color-warning、--color-info、--color-focus 等规范契约名称。组件只使用这些令牌,调色板来自 color-system。
- 选择产品基线:从默认支持的模式开始,在那里设定结构和角色关系。
- 调整每一种额外映射,绝不反转:对于深色,使用逐级变亮的表面阶梯、舒适的文本对比度,并重新调整强调色。
- 将作者定义的对比度与强制颜色分开:prefers-contrast: more 可以选择更高对比度的映射;forced-colors 由系统控制,因此应尊重其颜色。
- 有意识地解析偏好:使用 prefers-color-scheme,持久化可选主题,并设置 color-scheme 使原生控件跟随。
- 在每种主题的实际表面上进行验证。
使用方式
- 快速开始:使用 assets/theme-starter.css;保留角色、调整后的映射、强制颜色协作以及早期保存偏好解析。
- 早期保存主题解析:当存在持久化控件时,在渲染前应用已验证的覆盖。在兼容 CSP 的实现中,通常是在 <head> 中放置一个带 nonce 或哈希的小型内联脚本,而不是延迟加载的 bundle: <script>/* inline, in <head>, before CSS paint / try { const t = localStorage.getItem("theme"); if (t === "dark" || t === "light") document.documentElement.dataset.theme = t; } catch { / Storage is unavailable; CSS keeps the OS/application default authoritative. */ } </script>
- light-dark() 简写:当 color-scheme 驱动切换(没有持久化覆盖)时,light-dark() 可避免媒体查询重复:background: light-dark(oklch(96% .02 78), oklch(18% .024 64))。2024 年起浏览器广泛支持;与 [data-theme] 配合,以便显式选择和强制颜色覆盖。它不是在存在手动切换时的唯一机制。
- 参考文件:references/surfaces-and-elevation.md(深色阶梯、层级、边框、阴影限制)、references/color-and-contrast.md(深色强调色、状态色、对比度、OKLCH)、references/light-and-contrast.md(浅色映射、关系、强调色、渲染检查)、references/high-contrast.md(prefers-contrast + forced-colors;覆盖限制)、references/decision-records.md(元模式 + ADR 规则)。
- 账本:以五字段账本收尾,Reason 以检查 ID 开头,Evidence 中命名模式/环境。字段为 Status、Reason、Remediation、Evidence、Provenance。检查项包括 role-map、supported-maps、resolution、forced-colors、contrast-handoff。
- 交付方式:报告角色、映射、解析、错误主题闪烁行为以及每种主题的对比度,并在适用时提供作用域环境和强制颜色证明。将源阶梯、类型、技术可访问性和认知问题交给相邻技能。来源需注明且独立。
适用场景 适用于需要一致的浅色、深色、高对比度或强制颜色映射的界面。避免:请求是发明源调色板而不是映射语义角色。排除:不要反转浅色调色板并称之为深色模式。
限制 当语义角色缺失且主题值将是原始颜色副本时,应暂停并交接。接收 [color-system, design-system-interview, improve-existing-website, tasteroll],发送 [a11y-pass, cognitive-a11y, responsive-layout, component-states]。输出:每种支持主题的语义映射,加上强制颜色行为、偏好策略和测量对。证据:table_with_evidence,包含 status、reason、remediation、evidence、provenance。
非妥协原则
- 单一语义令牌来源;组件绝不硬编码颜色。重新主题化只重新映射令牌。
- 根据上下文选择浅色表面。纯色或柔化端点都可能有效;测试对比度、眩光、阅读舒适度和偏好。
- 深色是调整过的,不是反转的:近黑色可靠,但纯黑色可能适合 OLED、投影或强烈系统。用表面/边框区分层级,在适当处将文本调整到低于引起眩光的白色,并重新测量强调色。
- 发布默认值和解析策略。受支持的用户选择会持久化并优先于操作系统偏好;单主题产品不需要切换。
- 每种主题声明 color-scheme(原生控件/滚动条跟随)。
- 尊重 forced-colors/高对比度——不要覆盖系统颜色;有意识地使用 forced-color-adjust 并保持焦点可见。
- 每种主题重新验证对比度(正文 ≥4.5,大号/UI ≥3)针对该主题的表面——在浅色中通过的颜色在深色中常常失败,反之亦然。
解析与恢复 将角色名称、主题值和选择分开。记录优先级:系统 forced-colors;受支持的用户选择;操作系统偏好;应用默认值。对于控件,定义存储和回退。 强制颜色是一种环境,不是调色板:保持系统颜色/原生控件权威,并通过文本、图标、状态或形状保留警告、错误、选择和焦点。如果某个角色缺失,记录失败恢复行并停止;重用强调色会改变其含义。 尽早应用主题以最小化错误主题闪烁。说明预绘制行为、不可用的已保存选择以及原生控件 color-scheme。无闪烁需要一次冷加载观察,命名主题、产物和时间。
浏览器 chrome、选择和自定义滚动条是可选的润色,不是完成门槛。
契约(生成) 规范细节:contract.json。 路由:界面需要一致的浅色、深色、高对比度或强制颜色映射(+1 in contract.json);避免:请求是发明源调色板而不是映射语义角色(+1 in contract.json)。 排除:不要反转浅色调色板并称之为深色模式(+1 in contract.json)。 停止/交接:当语义角色缺失且主题值将是原始颜色副本时暂停(+1 in contract.json);接收 [color-system, design-system-interview, improve-existing-website, tasteroll] -> 发送 [a11y-pass, cognitive-a11y, responsive-layout, component-states]。 输出:每种支持主题的语义映射,加上强制颜色行为、偏好策略和测量对。 证据:table_with_evidence,包含 status、reason、remediation、evidence、provenance。