aria-disabled="true"本身不提供焦点能力,必须手动加tabindex="-1"或tabindex="0";原生button应直接用disabled属性,非原生元素需同时设aria-disabled="true"、tabindex="0"、pointer-events:none并拦截事件;pointer-events:none会静默阻止focus()。

aria-disabled="true" 本身不提供焦点能力,必须手动加 tabindex="-1"
很多人以为 aria-disabled="true 会让元素“看起来禁用但还能聚焦”,其实它连聚焦能力都不给——aria-disabled 只是告诉屏幕阅读器“这东西现在不能点”,对 DOM 焦点流完全没影响。原生不可聚焦元素(比如 div、span、a)加了它也不会自动变可聚焦。
想让置灰元素仍能被键盘用户 tab 到,唯一办法是显式赋予焦点能力:
-
tabindex="-1"是必须项:它让元素可通过脚本调用.focus(),且在某些浏览器中支持 Tab 进入(取决于是否在 Tab 顺序中显式参与) - 但注意:
tabindex="-1"不等于tabindex="0"——后者会把它插入默认 Tab 流,前者只是“允许聚焦,但不自动加入”;若需键盘用户能 tab 进去,应改用tabindex="0",除非你明确只希望脚本控制聚焦 - 如果元素本身是原生
button,它默认可聚焦;此时加tabindex="-1"反而会把它踢出 Tab 流,属于典型翻车操作
为什么 button 加 aria-disabled="true" 后反而不能聚焦了
因为你在破坏原生行为。原生 button 元素的禁用逻辑是:设 disabled → 自动移除焦点能力 + 屏幕阅读器读作“已禁用”。但如果你改成 aria-disabled="true" 却不配 disabled,浏览器不会自动处理焦点;更糟的是,如果同时写了 disabled 和 aria-disabled="true",部分读屏器会报“状态矛盾”,且 disabled 会强制接管并屏蔽焦点,aria-disabled 形同虚设。
所以结论很直接:
立即学习“前端免费学习笔记(深入)”;
- 用原生
button?直接改el.disabled = true/false,别碰aria-disabled - 非原生元素(如
div role="button")?必须同时设置:aria-disabled="true"+tabindex="0"(不是 -1)+pointer-events: none+ JS 拦截click和keydown - 视觉置灰只是副作用,不是焦点保障——灰了≠能聚焦,两者靠不同机制控制
CSS 里 pointer-events: none 会影响 focus() 吗
会,而且是静默失败。即使你调用了 el.focus(),只要元素或其任意祖先设置了 pointer-events: none,该调用就会无效(无报错,但 document.activeElement 不变)。这不是 bug,是规范行为:CSS 的 pointer-events 控制的是“是否参与交互事件流”,而焦点获取属于该流程的一部分。
修复方式很简单:
- 禁用时用
pointer-events: none防鼠标点击,但聚焦前临时移除它(例如在focus()前el.style.pointerEvents = 'auto',聚焦后再恢复) - 或者换用更安全的视觉禁用方式:
opacity: 0.5+cursor: not-allowed,保留pointer-events: auto - 检查父容器是否也设置了
pointer-events: none或inert—— 这些会阻断整个子树的焦点能力
focus() 失败但元素明明可见,怎么排查
常见原因不是代码写错,而是 DOM 状态或样式干扰:
- 元素未挂载到文档树:先
appendChild()再focus(),不要提前调用 - 父级有
inert或aria-hidden="true":它们会让子元素彻底退出可访问性树,focus()必然失败 - 元素被 CSS 移出渲染流:
display: none、visibility: hidden、opacity: 0通常不影响focus(),但position: absolute; left: -9999px可能导致offsetParent === null,此时多数浏览器拒绝聚焦 - 元素当前
tabindex值为-1且不在 Tab 流中:此时focus()能成功,但用户无法通过键盘 tab 到它——得看你的目标是“脚本聚焦”还是“键盘可达”
真正容易被忽略的,是 pointer-events: none 和 inert 这两个“静默杀手”:它们不报错、不警告,只让 focus() 像没发生一样。



















