只写 button:disabled 不够,必须同时声明 button:disabled 和 button[disabled],因 React/Vue 动态设 disabled 仅修改 DOM 属性而不写入 HTML 属性,SSR 或手动操作依赖 [disabled],二者覆盖场景不同,缺一不可。

只写 button:disabled 不够,必须同时声明 button:disabled 和 button[disabled],否则 React/Vue 动态禁用的按钮在 SSR 或混合渲染场景下会漏掉样式。
为什么 button:disabled 单独写经常失效
根本原因不是 CSS 写错了,而是 DOM 上没真实存在 disabled 属性。React 的 disabled={true} 和 Vue 的 :disabled="true" 都是直接设置 el.disabled = true,这会触发 :disabled 状态匹配,但不会自动写入 HTML 属性 disabled=""。而服务端渲染(SSR)生成的静态 HTML、或手动用 setAttribute('disabled', '') 添加的按钮,只响应 [disabled]。两者覆盖场景不同,缺一不可。
常见错误现象:
- 本地开发看着正常,部署到 SSR 环境后禁用按钮颜色/光标没变
- 按钮禁用了,鼠标悬停时仍显示高亮色(被
.btn:hover覆盖) - 移动端 iOS Safari 对
:disabled样式支持保守,[disabled]更稳
button:disabled, button[disabled] 必须显式控制哪些样式
浏览器对禁用态的默认样式极弱,仅禁用交互,不改视觉反馈。只靠 opacity: 0.5 是危险操作——它会让边框、阴影、图标全变灰,破坏对比度,还可能让屏幕阅读器误判内容可用性。
推荐组合(满足 WCAG 最低对比度要求):
-
color: #999+background-color: #f0f0f0+border-color: #ddd -
cursor: not-allowed(:disabled不自动改光标) -
pointer-events: none(慎用:会阻止 focus,仅用于纯触摸场景) -
transition: none(禁用所有过渡动画,避免悬停态残留)
如果按钮有行内样式如 style="cursor: pointer",:disabled 规则会被覆盖,得加 !important 或提升选择器权重(如 .btn:disabled)。
非原生按钮(如 div 或自定义组件)怎么办
:disabled 只匹配 button、input、select、textarea 这几类原生可禁用元素。给 div 加 disabled 属性完全无效,CSS 不识别,JS 也不阻止事件。
正确做法:
- 优先改用语义化
,别模拟按钮 - 若必须用自定义组件,用 class 控制状态:
,再配.is-disabled { cursor: not-allowed; color: #999; } - 同步设置
aria-disabled="true"和tabindex="-1",保证屏幕阅读器和键盘导航可感知 - JS 事件处理开头必须加
if (el.disabled || el.hasAttribute('aria-disabled')) return,不能只靠样式挡逻辑
移动端和 Safari 的特别坑点
iOS Safari 对 :disabled 的样式支持尤其不可信。比如 button[type="submit"] 在 里,opacity 常被忽略;filter: grayscale() 在 iOS 12–14 有闪烁或延迟,且高对比度模式下失效。
稳妥方案:
- 所有禁用态样式都写死,不依赖继承(比如明确写
background-color,别指望父级color透传) - 避免
filter,用opacity: 0.45+ 颜色组合兜底 - 若需彻底阻断触摸,加
pointer-events: none,但必须同步处理键盘焦点逻辑(加tabindex="-1"并在 JS 中跳过该元素) - 测试时真机连 Safari 开发者工具,检查 DOM 是否真有
disabled=""属性,别只看 React DevTools 的 props
最麻烦的从来不是怎么写 CSS,而是你有没有确认:DOM 属性存在、框架透传到位、无障碍属性同步、事件拦截写全、浏览器兼容兜底——漏掉任意一环,“看着不能点,结果点了有反应”就是必然结果。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











