原生语义标签是构建无障碍组件的唯一可靠路径;因其自动提供焦点管理、键盘交互、disabled同步及aria状态,而div+role需手动补全所有能力,极易遗漏关键环节。

用原生语义标签代替自定义role="button"和div模拟控件
可访问性错误一大半源于用div硬加role="button"、role="checkbox"等,这只会“声明”语义,不提供真实行为。键盘用户按空格或回车没反应,disabled属性无效,form.elements里查不到,读屏器播报也残缺。
正确做法是直接用button、input[type="checkbox"]、select等原生元素。它们自带焦点管理、键盘交互、表单集成和ARIA隐式角色——不用写一行JS就能通过WCAG 2.1 A级。
- 所有触发操作的节点,优先选
button;若需提交表单,加type="submit"或type="button" - 开关类控件必须用
input[type="checkbox"]或input[type="radio"],别用div+aria-checked - 导航区域必须用
nav,且带aria-label(如aria-label="主导航"),不能只靠CSS class命名
模板中强制约束label与input的显式关联
很多“表单不可读”问题不是因为没写label,而是for指向的id不存在、重复或拼错。浏览器不报错,但屏幕阅读器直接跳过输入框。
在<template></template>里封装表单项时,必须把label和input写成嵌套结构,或确保for与id由同一段逻辑生成,避免手动维护。
- 推荐写法:
<label>邮箱<input type="email" name="email"></label>(隐式关联,零出错) - 若需分离布局,用
data-id-suffix统一生成配对ID:<label for="email-{{suffix}}">邮箱</label><input id="email-{{suffix}}" type="email"> - 禁止在模板里留空
for=""或for="xxx"而无对应id——CI阶段用html-validate配label-has-for规则自动拦截
自定义元素必须桥接原生表单能力
写<x-toggle></x-toggle>或<x-date-picker></x-date-picker>时,如果没实现formAssociated = true和内部真实input桥接,它就只是个“视觉组件”,无法被form.checkValidity()识别,也不会响应reset()或setCustomValidity()。
漏掉任意一项,都会导致表单验证逻辑断裂、无障碍状态丢失、甚至提交时数据静默丢弃。
- 构造函数第一行必须
super(),且声明static formAssociated = true - Shadow DOM内必须挂一个真实
input[type="checkbox"](或其他合适类型),并通过get checked()/set checked(v)同步 -
attributeChangedCallback中处理布尔属性(如checked、disabled)时,判断逻辑必须是newValue !== null,不是!!newValue或newValue === "true"
构建阶段用html-validate拦截结构性缺陷
很多可访问性问题(比如p里嵌div、table缺tbody、img没alt)不是运行时能修的——浏览器已静默修正DOM,JS拿到的结构和源码完全不一致,aria-labelledby指向的元素可能根本不存在。
这类错误必须卡在代码提交前,靠构建工具链拦截,而不是等Lighthouse跑完再返工。
- 本地VS Code启用
HTMLHint插件,开attr-accessibility、attr-no-duplication、element-permitted-content规则 - CI中在Webpack/Vite构建命令前执行:
npx html-validate --config .htmlvalidate.json src/**/*.html - 关键规则要定制:例如
"p": ["#text", "br", "span", "em"],禁掉div出现在p里的非法嵌套
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











