表单无障碍核心是让屏幕阅读器准确播报字段用途、当前值、必填/错误状态,并确保键盘用户能自然聚焦、提交、跳转;缺失label时须用for关联或aria-label补救,禁用placeholder替代label,错误提示需用aria-invalid和aria-describedby实时绑定,按钮必须语义化或正确添加role与键盘事件支持。

表单无障碍不是加几个 aria- 属性就完事,核心是让屏幕阅读器能准确说出“这是什么字段”“当前值是什么”“是否必填/出错”,且键盘用户能自然聚焦、提交、跳转。
表单控件没 <label></label> 怎么补救
没有显式 <label for="xxx"></label> 关联的输入框,读屏器大概率只报“编辑框”,不告诉你它要填啥。placeholder 不是 label,视觉一输就消失,辅助技术也不认。
- 首选:补上带
for的<label></label>,比如<label for="email">邮箱地址</label><input id="email"> - 次选(仅限上下文极明确):用
aria-label,如搜索框<input type="search" aria-label="搜索网站内容"> - 禁用:
placeholder="邮箱"单独存在——它既不被所有读屏器识别为标签,也无法在输入后持续提供上下文 - 注意:如果 label 文本已可见(比如图标旁有“密码”文字),优先用
aria-labelledby="id-of-text",避免重复写描述
aria-invalid 和 aria-describedby 怎么配错误提示
光靠 CSS 高亮红框或弹个 Toast,视障用户完全感知不到错误。必须把错误信息和输入框建立可访问关联。
- 输入出错时,设
aria-invalid="true",别写"false"或留空——只有"true"才触发读屏主动播报 - 错误文案必须有唯一 ID,并通过
aria-describedby="error-email"指向它;不能只靠 class 或 DOM 位置猜测 - 错误容器不要用
display: none隐藏,而要用visibility: hidden或opacity: 0+aria-hidden="true",否则读屏器根本读不到 - 校验通过后,记得同时移除
aria-invalid和aria-describedby,否则会残留旧错误提示
按钮类交互元素为什么不能只靠 onclick
一个 <div onclick="submit()">提交</div> 对鼠标用户没问题,但键盘用户 Tab 进不去,Enter/Space 按了没反应,读屏器甚至不把它当可操作控件。
- 必须加
role="button"告诉辅助技术“这是按钮”,否则它默认是静态文本 - 加了
role="button"后,还得手动监听keydown,对Enter和Space都触发相同逻辑 - 更省事也更安全的做法:直接用
<button type="button">提交</button>——语义自带,键盘行为原生支持,不用补丁 - 如果是图标按钮(比如只有
<svg></svg>),必须配aria-label或aria-labelledby,否则读屏器可能读成“图形”或直接跳过
多步骤表单怎么让进度状态可感知
只靠颜色、粗细、CSS 动画高亮当前步骤,对读屏用户等于没标。他们需要明确知道“我现在在哪一步”“总共几步”“下一步怎么走”。
- 每步容器(如
<li>或<div>)必须设 <code>aria-current="step",值只能是"step",不是"true"或"page" - 当前步骤的主输入框(如邮箱字段)应通过
aria-describedby="step-2"关联到该步骤容器,容器本身要有简洁描述,如<li id="step-2" aria-label="步骤 2:联系信息"> - 切换步骤时,JS 必须手动移除旧节点的
aria-current,再设到新节点上;React/Vue 中注意 ref 或 key 更新时机,否则状态不同步 - 别用
role="progressbar"替代——那是给“上传完成 65%”这种数值型进度用的,多步骤是离散状态,强行套用会误导用户以为能拖动
最常被忽略的是错误提示与输入框的实时绑定关系,以及多步骤中 aria-current 的手动同步时机——DOM 改了,属性没跟上,读屏器就读不到最新状态。











