:disabled伪类仅对原生表单元素生效,自定义组件必须透传disabled属性至内部button等标签,否则不匹配;推荐使用[disabled]属性选择器以确保兼容性与可靠性。

:enabled 和 :disabled 伪类在自定义组件中根本不会自动生效——它们只对原生表单元素起作用,所谓“关键”,其实是提醒你:别指望它们能帮你省事,反而得主动处理透传和兼容性。
:disabled 不匹配自定义标签的底层原因
浏览器实现 :disabled 是基于 HTML 规范的语义约束,不是靠属性存在与否做简单判断:
- 只有
button、input(除type="hidden")、select、textarea、optgroup、option这几类原生元素被 UA 样式表注册为“可禁用”; -
<my-button disabled></my-button>或<div disabled></div>中的disabled属性只是普通 HTML 属性,CSS 引擎直接跳过匹配; - 即使你在 React/Vue 里写了
disabled={true},若没落到内部button标签上,最终渲染出的 DOM 里就没有disabled属性 →:disabled规则永远不触发。
自定义组件中必须透传 disabled 的实操要点
框架不会自动把 props 映射为 DOM 属性,必须显式绑定:
-
React:
立即学习“前端免费学习笔记(深入)”;
- ❌ 错误写法:
<button>{children}</button>(完全没管disabled) - ✅ 正确写法:
<button disabled={disabled} {...restProps}>{children} - 注意:如果用了
useTransition或shouldForwardProp,要确保disabled没被过滤掉
- ❌ 错误写法:
-
Vue:
- ❌ 错误写法:
<button v-bind:class="{ disabled }"></button>(只是加 class,没设属性) - ✅ 正确写法:
<button :disabled="isDisabled" v-bind="$attrs"></button> - Vue 3 中若用
defineOptions({ inheritAttrs: false }),必须手动v-bind="$$attrs"
- ❌ 错误写法:
-
Svelte:
- ✅ 推荐:
<button bind:disabled="{disabled}">{children}</button>或<button disabled>{children}</button>
- ✅ 推荐:
共同验证动作:打开开发者工具,检查最终渲染出的
button标签上是否真实存在disabled属性(不是只挂在自定义组件根节点上)。
为什么更推荐用 [disabled] 而非 :disabled
[disabled] 是属性选择器,行为更确定、兼容性更好,尤其适合封装场景:
匹配任意含
disabled属性的元素,包括<my-button disabled></my-button>、<div disabled></div>、<span disabled></span>;IE8+ 支持,且不受旧版 WebKit(如 Safari < 15.4)对伪类重绘延迟的影响;
-
但要注意:JS 动态设置
el.disabled = true不会自动添加disabled属性,必须配合:el.setAttribute('disabled', '')- 或更现代的写法:
el.toggleAttribute('disabled', true)
避免混用:
button:disabled, button[disabled]是冗余的,选一个并坚持用到底。
真正容易被忽略的是:禁用状态切换后,焦点可能还留在该元素上(尤其是键盘导航或屏幕阅读器场景),仅靠 CSS 无法解决。这时候需要 JS 监听 focusin 并主动 element.blur() 或 focus() 到下一个可用元素——样式只是表层,可访问性才是硬门槛。


















