:disabled伪类本身不自动置灰,需配合opacity、filter、color等样式实现;失效主因是元素非原生可禁用类型或disabled属性未真实透传到dom。

:disabled 伪类本身不会自动“置灰”,它只是个钩子——样式是否生效、灰得是否合理,全看你怎么写、怎么用、以及元素是否真被禁用了。
为什么写了 button:disabled { filter: grayscale(1) } 却没反应?
最常见原因是按钮根本没真正进入禁用状态。浏览器只对原生支持 disabled 属性的元素(<button></button>、<input>、<select></select>、<textarea></textarea>)响应 :disabled;如果你用的是 <div> 或封装组件,哪怕加了 <code>disabled="true" 属性,CSS 也完全不匹配。
实操建议:
- 检查 DOM:打开 DevTools,确认按钮标签上真有
disabled属性(不是disabled="false",也不是字符串值) - React/Vue 中别只传
disabled={isDisabled},要确保它透传到原生<button></button>标签上(Vue 用:disabled="isDisabled",React 确保是布尔值而非字符串) - 动态禁用时,用
el.disabled = true,别只改 class 或 style
怎么写才让灰度效果稳定又友好?
单靠 filter: grayscale() 风险高:iOS Safari 旧版可能全黑,高对比度模式下会被系统忽略,且它不改光标、不阻焦点、不改语义。
推荐组合写法:
- 用
opacity: 0.6降低整体透明度(兼容性最好,所有浏览器都认) - 叠加
filter: grayscale(80%) brightness(0.95),避免 Safari 过暗 - 显式定义
color和background-color,比如color: #999、background-color: #f5f5f5,确保深色主题或高对比度模式下仍有明确反馈 - 必须加
cursor: not-allowed,否则用户仍会误以为可点 - 禁用过渡动画:
transition: none,防止启停时样式跳变
为什么必须同时写 button:disabled 和 button[disabled]?
:disabled 匹配当前处于禁用状态的元素(JS 动态设 el.disabled = true 也能响应),而 [disabled] 只匹配 HTML 中写了该属性的元素(比如 SSR 渲染的静态 HTML、或手动 setAttribute('disabled', ''))。两者行为不完全重合,漏掉任一都会导致部分场景样式失效。
实操建议:
- 生产环境务必同时写:
button:disabled, button[disabled] - 扩展到其他控件:
input:disabled, input[disabled], select:disabled, select[disabled] - 自定义组件加类名支持:
.my-button:disabled, .my-button[disabled]
灰度只是表层,禁用状态要三者一致
只改视觉,按钮依然能聚焦、键盘能触发、屏幕阅读器会播报“可用”——这不是禁用,是障眼法。
真正可靠的禁用,必须同步三个层面:
- DOM 层:原生元素设
disabled属性;非原生元素(如<div role="button">)设 <code>aria-disabled="true"+tabindex="-1" - CSS 层:用
:disabled或自定义 class 控制视觉,同时加cursor: not-allowed - 行为层:事件处理器开头加
if (el.disabled) return,或确保点击逻辑被原生禁用拦截
最容易被忽略的是:禁用后仍保留焦点样式(比如 :focus:disabled 没重置),或者图标颜色没随 color 变化(SVG 里没写 fill: currentColor)。











