加了tabindex="0”只是让元素进入Tab流,不等于能响应按键或有视觉反馈;必须同步监听keydown、区分Enter/Space并preventDefault()、设置role和aria属性,四者缺一不可。

自定义控件默认不可键盘访问,加 tabindex="0"、监听 keydown、区分 Enter 和 Space、同步 aria- 属性,四者缺一不可。
为什么加了 tabindex="0" 还是进不去?
加了只是“能进 Tab 流”,不代表它能响应按键或有视觉反馈。常见断点:
- 没手动监听
keydown,浏览器不会自动把空格/回车转成点击 - 监听用了箭头函数,
this指向丢失,事件 handler 实际没执行 - 组件在
shadowRoot里,但监听绑在了 light DOM 节点上,事件根本收不到 - 父容器或 body 初始无焦点,导致键盘事件压根没派发到你的元素(尤其全局监听时)
如何正确监听 keydown 并处理 Enter/Space?
必须在 connectedCallback 中绑定,且只拦截明确需要接管的行为:
- 用
this.addEventListener('keydown', this.handleKeydown),别用onkeydown=""或箭头函数 -
handleKeydown里判断:if (e.key === 'Enter') { e.preventDefault(); this.clickHandler(); } - 同样处理
' '(空格字符),不是'Space'—— 后者是旧规范,已不推荐 - 仅对
e.target === this的情况响应;若内部有<input>,需先e.stopPropagation()
event.key 和 event.code 到底怎么选?
混用会导致非英文键盘用户完全失效:
立即学习“前端免费学习笔记(深入)”;
- 快捷键(如 Ctrl+S):用
e.ctrlKey && e.code === 'KeyS',e.key === 's'在德语键盘可能返回';' - 方向键/功能键(ArrowDown、Escape、F5):只看
e.code,它与布局无关 - 纯字母输入逻辑(如搜索框输入过滤):才用
e.key,它反映实际字符 - 绝对不用
e.keyCode或e.which,Chrome/Firefox 已返回0
下拉菜单这类复杂组件,光监听不够
键盘可访问性不只是“能按”,还要让屏幕阅读器理解状态:
- 触发按钮加
role="combobox"、aria-haspopup="listbox"、aria-expanded="false" - 下拉容器设
role="listbox"和唯一 ID,与aria-controls对应 - 每个选项用
role="option",并实时同步aria-selected="true" - ArrowUp/Down 时用
element.focus()主动切焦点,不是靠 CSS:focus - 收起菜单后,必须
triggerButton.focus()把焦点还回去
最常被跳过的环节是 aria-selected 同步和 focus() 主动管理——这两处一漏,NVDA 或 VoiceOver 就会读错当前选中项,键盘用户无法确认自己点了什么。



















