eslint-plugin-jsx-a11y与html-validate必须协同使用:前者专检JSX动态逻辑(如aria-hidden={isMenuOpen ? 'false' : 'true'}),后者覆盖纯HTML片段(如SSR模板、CMS输出),二者互补缺一不可,共同构成全链路无障碍质量门禁。
HTML 代码质量不会靠“重写一遍”自动提升,关键在约束机制和渐进式落地路径。没有统一语义规范、不绑定构建流程、不接入 CI 检查的重构,90% 会退化回老样子。
eslint-plugin-jsx-a11y 和 html-validate 必须同时用只用 eslint-plugin-jsx-a11y 覆盖不了纯 HTML 片段(如服务端渲染模板、CMS 输出块、遗留 innerHTML 注入),而只用 html-validate 又抓不到 JSX 层面的动态属性逻辑(比如 aria-hidden={isMenuOpen ? 'false' : 'true'} 写反了)。
实操建议:
html-validate 的 CLI 模式扫描 src/**/*.html 和 public/**/*.html,配置 "valid-lang": "error"、"require-skip-link": "warn" 等强规则jsx-a11y/aria-props、jsx-a11y/heading-has-content,并把 rulesDirectory 指向团队自定义规则(例如禁止 div[role="button"] 而未绑定 onKeyDown)aria-required-child 错误,一个报 duplicate-id,都阻断 PR 合并data-* 属性不变成新混乱源重构常把旧 class 名批量替换成 data-component 或 data-track-id,结果半年后满屏 data-xxx,语义比 class 还难懂。
立即学习“前端免费学习笔记(深入)”;
实操建议:
data-ui-(仅用于组件边界识别)、data-track-(仅埋点)、data-test-(仅测试定位),其他一律禁止data-ui-* 必须在设计系统文档中注册,例如 data-ui-tabs-container 对应 Tabs 组件根节点,且该值不可由业务层覆盖data-,匹配正则 /^data-(?!ui|track|test)/ 并报错<section> 和 <article> 时,什么情况下必须加 aria-labelledby
当 <section> 不含标题元素(<h1>–<h6>),或标题是视觉隐藏但未用 aria-label/aria-labelledby 关联时,屏幕阅读器无法建立内容层级,会导致跳过整块区域。
常见错误现象:
<section class="card"> 包裹,内部只有 <div class="card__title">用户统计</div>(无 heading 标签)<section> 用 CSS 隐藏标题文字,仅靠图标示意实操建议:
<h1>–<h6> 的 <section> 或 <article>,必须有 aria-labelledby 指向页面内真实存在的 ID,且该 ID 所在元素需有可读文本(不能是空 <span aria-hidden="true">)html-validate 规则 "require-valid-alt" 扩展为 "require-section-label" 自定义规则,在 CI 中检查Section 组件默认渲染 <section aria-labelledby={id}>,id 由组件自动生成并注入对应 <h3 id={id}>,业务方不可绕过真正卡住 HTML 质量跃迁的,从来不是语法对错,而是「谁有权改」和「改错谁负责」——所有校验规则必须绑定到具体角色(设计师提 PR 时触发无障碍扫描,前端提交时触发语义结构检查),否则再好的规则也只是一份 PDF。