必须同时写 button:disabled 和 button[disabled],因框架动态设 disabled 不透传 html 属性,ssr 渲染也仅含属性无 js 状态;仅用前者会漏掉大量真实禁用按钮,导致样式失效、光标仍为手型或点击仍响应。

只写 button:disabled 会漏掉大量真实禁用的按钮,必须同时写 button:disabled 和 button[disabled],否则 SSR、React/Vue 动态设 disabled 或手动 setAttribute 的场景全失效。
为什么 button:disabled 单独写经常没反应
浏览器只对原生可禁用元素(button、input、select、textarea)识别 :disabled 伪类,但框架里 disabled={true} 往往不透传为 HTML 属性——它只是 React/Vue 的 props 值,DOM 上没有 disabled 属性。而 button[disabled] 能捕获所有含该属性的节点,包括服务端渲染的静态 HTML、el.setAttribute('disabled', '')、甚至自定义组件如 <mybutton disabled></mybutton>。
- 常见错误现象:本地开发正常,上线后部分按钮没变灰、光标仍是手型;或点击后仍有响应
- 不要依赖
:not(:disabled)反向控制启用态,容易被更高权重选择器覆盖 - 若用了自定义组件(如
.my-button),需扩展为.my-button:disabled, .my-button[disabled]
禁用样式不能只靠 opacity 或 filter
opacity: 0.5 会让文字对比度跌破 WCAG 4.5:1(尤其深色模式下不可读);filter: grayscale(100%) 在 iOS Safari 旧版常渲染失败或全黑,且两者都不改鼠标指针、不阻断键盘焦点、不屏蔽屏幕阅读器播报。
- 真正可靠的组合是显式控制多个通道:
color: #999+background-color: #f5f5f5+border-color: #ddd - 必须加
cursor: not-allowed(浏览器不会自动加) - 加
transition: none防止悬停动画残留 -
pointer-events: none慎用——它会让 Tab 键跳过该按钮,破坏键盘导航
非原生按钮(div、自定义组件)根本匹配不到 :disabled
:disabled 只对原生表单控件生效。给 <div role="button"> 或封装组件加 <code>disabled 属性,CSS 完全无视。
- 优先换回语义化
<button type="button"></button>,别用div模拟按钮 - 必须用自定义组件时,用 class 控制状态:
<my-button class="is-disabled"></my-button>,再配.is-disabled { cursor: not-allowed; color: #999; } - 同步加
aria-disabled="true"和tabindex="-1",否则屏幕阅读器和键盘用户感知不到禁用 -
fieldset[disabled]下的子元素不触发:disabled—— 这是 HTML 规范行为,不是 CSS bug
最易被忽略的是:禁用状态必须 DOM 层(disabled 属性或 aria-disabled)、CSS 层(显式多属性控制)、行为层(JS 中 if (el.disabled) return)三者严格一致,缺一环就会出现“看着不能点、实际能点”或“读屏器说能点、但点不动”的问题。











