应将主题色定义在 :root,用伪类通过重设 CSS 变量(如 --primary)统一控制状态色,配合 :is() 合并选择器;:active 需加 transition 和 touch-action 才可见;暗色模式用 prefers-color-scheme 媒体查询包裹 :root 及伪类规则;:visited 不支持修改自定义变量。

怎么用 :root 和 :is() 统一管理主题色变量
主题色不是写死在每个选择器里的,而是靠 CSS 变量 + 伪类联动实现的。核心是把颜色定义在 :root,再用伪类(比如 :hover、:focus、:disabled)去覆盖对应变量值——不是改样式,是改变量本身。
常见错误是直接在按钮上写 button:hover { color: var(--primary-hover); },结果每换一个状态就得加一堆重复规则。正确做法是让所有用到主色调的组件都读同一个变量,比如 color: var(--primary); background: var(--primary-bg);,然后只在伪类里重设变量:
:root {
--primary: #007bff;
--primary-bg: #007bff;
}
button:is(:hover, :focus-visible) {
--primary: #0056b3;
--primary-bg: #0056b3;
}
button:disabled {
--primary: #6c757d;
--primary-bg: #e9ecef;
}
-
:is()能合并多个伪类逻辑,避免重复声明;不支持旧版 Safari 可改用逗号分隔 - 变量名要带语义(如
--primary),别用--color-1这类无意义命名 - 注意变量作用域:伪类内重定义的变量只影响该选择器及其后代,不影响全局
:root
为什么 :active 状态经常“看不见”变化
因为 :active 触发时间极短(毫秒级),且默认没有过渡效果,人眼几乎无法察觉。更关键的是,如果父元素没设 cursor: pointer 或没监听点击事件,部分浏览器(尤其是 iOS Safari)会跳过 :active 样式。
- 必须配合
transition才能看清变化,比如transition: background-color 0.1s ease; - 移动端需要给触发元素加
touch-action: manipulation;,否则可能被系统拦截 - 不要依赖
:active做重要状态反馈(比如表单提交中),它不可靠;该用 JS 控制的就用is-loading类
如何让暗色模式和用户偏好同时生效
伪类和媒体查询不冲突,但顺序很重要。优先级是:用户强制设置(prefers-color-scheme) > 元素自身伪类 > 默认 :root。所以得把暗色模式逻辑放在最外层,再让伪类在内部微调。
立即学习“前端免费学习笔记(深入)”;
@media (prefers-color-scheme: dark) {
:root {
--primary: #0d6efd;
}
button:is(:hover, :focus-visible) {
--primary: #0a58ca;
}
}
- 别在
@media外再写一遍暗色变量,否则会被覆盖 - 如果项目支持手动切换主题,用 class 切换比媒体查询更可控,比如
html[data-theme="dark"] -
prefers-color-scheme在 Chrome 81+、Firefox 96+ 支持良好,但 IE 完全不支持,需降级兜底
哪些伪类不能直接改 CSS 变量
:visited 是唯一被浏览器明确限制修改变量的伪类——出于隐私考虑,CSS 变量在 :visited 中只能访问极少数安全属性(如 color、background-color),且不能读取或修改自定义变量。
- 试图写
a:visited { --primary: purple; }会静默失败,变量值不变 - 想给已访问链接换色,只能用
color或background-color直接赋值,不能间接通过变量 -
:target、:first-child这类结构性伪类可以改变量,但它们不反映用户交互状态,慎用于主题控制
真正难的不是写对语法,而是想清楚哪些状态该由 CSS 自动响应,哪些必须交还给 JS 控制——比如登录态切换、权限导致的禁用逻辑,硬塞进伪类只会让样式表越来越难维护。









