禁用按钮应避免混用disabled与aria-disabled:disabled彻底移除tab流且focus无效;若需视觉禁用但键盘可达,须用aria-disabled="true"+tabindex="-1"+手动事件拦截+css控制。

disabled属性会让按钮彻底退出Tab流,别指望JS能focus()它
原生
如果你真需要“视觉禁用但键盘可达”,就不能用disabled,得换方案:
- 移除
disabled,改用aria-disabled="true" - 同步设
tabindex="-1"(防止它进Tab流) - 手动监听
keydown和click,遇到aria-disabled="true"就e.preventDefault() - 用CSS
[aria-disabled="true"]控制灰化、cursor: not-allowed等视觉状态
aria-disabled="true"只是语义标记,不阻止点击也不禁焦点
aria-disabled="true"只告诉屏幕阅读器“这个控件当前不可用”,浏览器完全忽略它对交互的控制:点击照常触发、空格/回车照样响应、Tab键仍能聚焦、样式也不会变。它不能替代disabled,只适用于两类场景:
- 元素本身不支持
disabled属性,比如<div role="button">、第三方UI库封装过的按钮 <li>需要保持特定视觉样式(如灰色但不走UA默认灰度),又得让读屏器感知状态</li> <p>真正可用的禁用按钮(非原生)至少要三者齐备:</p> <ul> <li> <code>role="button"(告诉读屏器这是按钮) -
aria-disabled="true"(语义层传达状态) -
tabindex="-1"(避免键盘用户停在不可用处) - CSS加
pointer-events: none+opacity: 0.5(行为与视觉双控) - 所有事件监听器里手动检查:
if (el.getAttribute('aria-disabled') === 'true') return
混用disabled和aria-disabled会出问题
写了disabled又写aria-disabled="true"看似保险,实则冗余且可能引发读屏器警告。更危险的是只写aria-disabled="true"却忘了加tabindex="-1"——键盘用户Tab到它后按回车,照样触发click,纯属“假禁用”。
还有几个高频翻车点:
-
aria-disabled="false"是无效值,WAI-ARIA规范只要求"true"或不写;显式写false反而可能误导读屏器 - React中应写
aria-disabled={isDisabled ? "true" : undefined},而非{String(isDisabled)} - Vue中用
:attr="{ 'aria-disabled': isDisabled ? 'true' : null }",避免渲染aria-disabled="false" -
div或span加disabled属性完全无效,:disabled伪类也匹配不到——这类元素必须靠class+tabindex+aria-disabled组合实现
自定义组件禁用时必须同时设aria-disabled和disabled属性
像<x-toggle></x-toggle>这类自定义元素,若要纳入表单流程(被form.elements收录、响应reset()),必须声明static formAssociated = true,并手动同步value、checked、disabled等属性。
禁用状态尤其关键:必须同时设aria-disabled="true"和原生disabled属性,二者缺一不可。因为:
-
disabled确保表单提交时该控件值不被发送 -
aria-disabled="true"确保屏幕阅读器播报“已禁用” - 仅靠其中一个,都会导致可访问性断层:要么读屏器沉默,要么表单数据污染
最容易被忽略的是attributeChangedCallback里对布尔属性的判断——disabled的newValue永远是字符串"true"、null或空字符串,不是布尔值。正确写法是this.hasAttribute('disabled'),而不是newValue === true。











