自定义元素需手动绑定键盘事件并管理焦点与aria状态。必须设置tabindex、监听keydown/keyup、正确处理event.code与event.key、精准preventdefault、同步更新aria属性,四者缺一不可。

用 customElements.define() 注册后必须手动绑定键盘事件
自定义元素(Custom Element)本身不自动继承 keydown 或 keyup 行为,哪怕它渲染成 <button></button> 或 <input>。浏览器只对原生可聚焦、可交互的标签(如 button、input、textarea、带 tabindex 的元素)触发键盘事件。所以即使你写了 <my-input></my-input>,默认点不动、按不了——得自己加 tabindex 并监听。
实操建议:
- 在
connectedCallback里给this设置tabindex="0"(或根据语义设为-1),否则无法获得焦点 - 用
this.addEventListener('keydown', this.handleKeydown),别用箭头函数,否则this指向会错乱 - 记得在
disconnectedCallback中移除监听,避免内存泄漏 - 如果元素内含子控件(如内部有
<input>),需判断event.target === this再处理,否则会重复响应
event.key 和 event.code 别混用,尤其涉及修饰键和国际化
event.key 返回的是“逻辑字符”,比如按 Shift+2 在美式键盘是 @,在德语键盘可能是 ";而 event.code 固定返回物理按键名(如 Digit2),与布局无关。做快捷键(如 Ctrl+S)必须用 event.code + 修饰键判断,否则用户换键盘布局就失效。
常见错误现象:在非英文系统下,event.key === 's' 在按 Ctrl+S 时可能不成立,但 event.code === 'KeyS' && event.ctrlKey 始终可靠。
实操建议:
- 快捷键判定优先用
event.code(如'ArrowUp'、'Enter'、'KeyF')配合event.ctrlKey/event.shiftKey - 输入内容解析(如富文本编辑器中插入符号)才用
event.key - 避免写
event.key === ' '判空格——改用event.code === 'Space',更稳定
阻止默认行为要精准,别一上来就 event.preventDefault()
比如实现一个自定义下拉菜单,按 ArrowDown 展开选项,但若在 input 内部已聚焦,此时按方向键本该移动光标——你全局拦截了,就破坏了原生体验。
实操建议:
- 先检查
event.target是否是你想接管的上下文(例如event.target === this或event.target.closest('my-select')) - 只对明确需要覆盖的行为调用
event.preventDefault(),比如Enter提交表单时,才阻止其默认提交 - 对
Tab键通常不要阻止——应靠tabindex和focus()控制流,而不是劫持切换逻辑 - 测试时用真实键盘(别只靠 DevTools 的 Events 面板模拟),某些组合键(如
Alt+Tab)在模拟中不触发keydown
可访问性不是附加项,role 和 aria-* 必须随键盘逻辑同步更新
仅支持键盘操作还不够。屏幕阅读器依赖 role 和 aria-expanded 等属性理解当前状态。比如自定义折叠面板按 Space 切换展开,但没同步设 aria-expanded="true",NVDA 就读不出变化。
实操建议:
- 用
this.setAttribute('aria-expanded', String(isOpen))而非this.ariaExpanded = ...(后者不触发 ARIA 变更通知) - 动态角色(如从
role="button"切换到role="menuitem")需先移除旧role再设新值 - 所有键盘可触发的操作,都要有对应的
role(button、checkbox、slider等),不能全靠div+tabindex - 用 Chrome 的 Accessibility Inspector 实时验证属性是否生效,别只靠代码写对了就认为 OK
复杂点在于:键盘逻辑、焦点管理、ARIA 状态、DOM 更新这四者必须严格同步。漏掉任意一环,用户要么按不动,要么按了没反馈,要么读屏器完全“看不见”你的交互——而且这种问题在线上环境才明显暴露,本地开发容易忽略。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











