高对比样式不能塞进主题class里,因为高对比是系统级强制行为,与主题切换无关;必须独立于data-theme,直接作用于html或body,并用@media (prefers-contrast: high)和@media (forced-colors: active)分别处理,确保所有主题下均生效。

为什么不能把高对比样式塞进主题 class 里
因为高对比度是系统级强制行为,和主题切换完全无关。你写 .theme-dark .btn 再加 @media (prefers-contrast: high),结果是:系统开启高对比后,.theme-dark 还在,但所有 background-color 已被清空,而你的高对比规则又受限于 .theme-dark 这个 class——它可能根本没生效,或者只在深色主题下生效,漏掉浅色主题下的高对比用户。
真实场景中,用户可能开着 Windows 高对比主题 + 浏览器默认浅色主题,这时只有 @media (prefers-contrast: high) 规则该起作用,跟 data-theme 值无关。
- 高对比规则必须独立于
data-theme,直接挂在html或body上 - 不要用
[data-theme="dark"] @media (prefers-contrast: high)这种嵌套写法——媒体查询不支持嵌套在属性选择器内部 - 确保高对比 CSS 在所有主题样式表的最末尾加载,否则会被前面的
background-color覆盖(即使媒体查询匹配了)
DOM结构必须保留语义锚点,不能靠 class 动态增删
高对比模式下,屏幕阅读器和浏览器会更严格地依赖语义标签做导航与聚焦。如果你用 JS 把 <nav></nav> 替换成 <div class="nav"> 来适配主题,那在高对比下,这个区域就失去了“导航区”语义,键盘 Tab 顺序错乱、VoiceOver 不再把它识别为可跳转区块。
<p>同样,表单控件如果被框架包裹成 <code><div class="input-wrapper">
<input>,原生 <code>input 的 focus 状态可能无法正确触发 outline,而高对比下 outline 又是唯一可靠的焦点指示。
- 所有交互元素必须用原生语义标签:
<button></button>、<input>、<select></select>、<label for="id"></label> - 避免用
div[role="button"]替代button,高对比下 focus 管理和键盘行为不可靠 -
<main></main>、<nav></nav>、<aside></aside>这些标签不是装饰,是高对比环境下的 DOM 结构基线——它们的层级关系决定了焦点流和阅读顺序
@media (prefers-contrast: high) 和 @media (forced-colors: active) 怎么共存
别只写一个。Chrome 101+ 支持 @media (prefers-contrast: high),但旧版 Chromium(尤其是 Windows 上的 Edge Legacy 和某些企业定制 Chrome)只响应 @media (forced-colors: active)。后者更激进:连 CSS 自定义变量、background-image、甚至部分 color 值都可能被系统覆盖。
两个媒体查询要分开写,且规则尽量一致;但 forced-colors 下要额外加固:比如禁用 text-shadow(系统可能忽略),用 border 而非 box-shadow 做分隔,所有颜色值显式声明(别依赖 --primary 变量)。
- 先写
@media (prefers-contrast: high),再写@media (forced-colors: active),后者作为 fallback - 在
forced-colors规则里,避免使用!important——系统级样式可能直接无视它 - 测试时务必打开 Windows 设置 → 辅助功能 → 高对比度(选任一主题),而不是仅靠 DevTools 模拟
表单控件和按钮的 border 必须显式重置,不能靠继承
原生 input、select、button 在高对比下默认边框常被设为透明或 1px 极细,系统不会帮你加粗。光写 color: #000 没用,用户看不到控件在哪、有没有聚焦、是否禁用。
更麻烦的是,不同浏览器对原生控件的高对比渲染逻辑不一致:Firefox 可能保留一点 border,Chrome 则几乎全清空,Safari 有时连 outline 都压不住系统样式。
- 统一给
input, select, textarea, button声明border: 2px solid CanvasText(CanvasText是系统关键词,比写死#000更兼容) - 焦点状态用
outline: 2px solid Highlight+outline-offset: 2px,防止被系统 outline 合并吞掉 - 禁用态不要只靠
opacity: 0.5,要同步改border-color: GrayText和color: GrayText
真正卡住项目的,往往不是要不要加高对比,而是忘了高对比下 DOM 结构本身就成了无障碍链路的基础设施——语义标签不能动、原生控件不能包、border 和 outline 不能省。系统强制重绘时,它只认这几样东西,别的全是幻觉。











