语义、可访问名称与结构
可访问性先从浏览器能否理解界面开始。原生 button 自带焦点、键盘激活和按钮语义;把 div 加上 role="button" 只能补一部分语义,还要自己处理 tabindex、Enter/Space 和禁用状态。优先使用能表达意图的 HTML。
html
<label for="email">邮箱</label>
<input id="email" name="email" type="email" autocomplete="email" required />
<button type="submit">订阅</button>可访问名称来自可见文本、label、aria-label 或 aria-labelledby 等规则;不要同时堆多个来源制造冲突。装饰图标使用空 alt 或 CSS,传达信息的图片要写能替代图像信息的 alt。
练习
把一个“放大镜图标按钮”和一个没有 label 的搜索输入改成可访问版本,并写出键盘和读屏用户能观察到的名称。
核心机制与实现
无障碍实现是语义、键盘、焦点、名称、状态和动态反馈的组合;ARIA 只能补足缺失语义,不能替代原生元素。
围绕“语义、可访问名称与结构”先写出输入 → 中间状态 → 可观察结果,再实现最小闭环。不要把事件监听、异步请求、DOM 读取或 store 写入混在一个函数里;每个副作用都要有创建、完成、取消和清理路径。
html
<button aria-expanded="false" aria-controls="menu">菜单</button>
<div id="menu" hidden>...</div>
<script>
button.addEventListener('click', () => {
const open = button.getAttribute('aria-expanded') === 'true'
button.setAttribute('aria-expanded', String(!open))
menu.hidden = open
})
</script>逐步运行:先只验证成功路径,再改变一个输入;记录时间顺序、对象身份、错误类型和最终可见状态。若环境不支持某个浏览器 API,要把静态协议验证和真实浏览器验证分开记录。
失败边界与变体
- 输入为空、重复、过期或非法时,不应静默覆盖有效状态。
- 组件卸载、路由离开、标签页关闭或请求取消时,资源必须停止写入。
- 跨文档、Shadow DOM、Worker、SSR 或多标签页会改变可见边界,不能沿用单页面直觉。
- 需要持久化时记录 schema 版本和迁移失败策略;需要全局状态时先证明局部状态不足。
可运行实验与练习
使用键盘完成打开、移动、关闭和返回焦点;用辅助技术或浏览器无障碍树核对名称、角色、状态、错误和动态更新。
练习要求:实现一个最小版本,补一个失败测试,再给出一个替代方案及其适用条件。答案应包括实际输入序列、观察结果、修复原因和未覆盖的浏览器差异。