lang属性必须为IETF标准格式、<main>须为<body>直接子元素且唯一、标题层级必须连续,三者是可访问HTML的硬性准入门槛,缺一不可。

重构前必须验证的三个硬性准入点
不检查这三个点就开干,90% 的重构会直接让屏幕阅读器用户“失明”。lang属性格式错误、main嵌套或重复、标题层级断裂——这三者不是优化项,是可访问性的生死线。
常见错误现象:lang="zh" 或 lang="ch" 被动态模板拼出来;服务端 include 多次导致两个 <main></main>;CSS 隐藏了 <h3></h3> 但 DOM 仍存在,造成 H2 → H4 跳层。
-
lang必须是 IETF 标准格式:zh-CN、zh-TW、zh-HK,后缀不可省略,且必须写在标签上 -
<main></main>必须是的**直接子元素**,全局唯一;禁止出现在<article></article>、<section></section>或<div class="wrapper"> 内部 <li>标题层级必须连续:若视觉上不需要 <code><h3></h3>,就删 DOM,不要仅用display: none;JS 动态插入标题时,必须校验前一个标题级别 - 用
html-rewriter(Cloudflare Workers)或parse5 + estree-walker(Node.js)基于 AST 操作,而非字符串匹配 -
<header></header>必须包裹站点标识或主标题,不能当样式容器用;class值保留(如<div class="header navbar-fixed"> → <code><header class="navbar-fixed"></header>),避免样式断裂 -
<nav></nav>只包裹主导航链接组;面包屑、分页、文章内跳转链接不属于它,误用会让 NVDA 的N键失效 - Git pre-commit hook 里跑
npx html-validate --config .htmlvalidate.json src/**/*.html,本地就能拦截低级提交 - 对 legacy class 建立白名单(如
["btn-primary", "text-muted"]),允许保留但禁止新增;新 class 必须符合 BEM 规范,否则 lint 报错 - 把重构后的 HTML 片段存为 Storybook 组件,每次修改触发截图比对,视觉回归自动发现结构意外变动
- 动态创建按钮必须用
document.createElement('button'),而不是div+onclick;确保它能被 Tab 键聚焦 - 模态框打开后,用
modalTitle.focus()将焦点移至内部第一个可聚焦元素;关闭时焦点必须回到触发源 - 实时更新区域(如状态提示)必须带
aria-live="polite";下拉菜单需同步设置aria-expanded和aria-controls
语义标签替换不能靠正则,得靠上下文判断
把所有 div class="header" 批量替换成 <header></header> 是最典型的倒退操作——它可能把模态框里的 div、评论区的 div 全都误伤,结果导航结构被炸穿。
使用场景:只在 直接子级、且无 data-no-rewrite 属性的 div 上做替换;含 onclick 的容器先加 data-legacy-js 标记,不删逻辑。
立即学习“前端免费学习笔记(深入)”;
CI 和本地钩子必须卡住新问题的引入
人工改完一次,下次 PR 又冒出一堆 div class="card-body",说明防线没建起来。自动化重构的终点不是改完老代码,而是让新代码天生合规。
性能 / 兼容性影响:html-validate 在 CI 中启用 "semantic-elements": "error" 规则后,单次校验耗时约 120–300ms(取决于 HTML 文件数),不影响主流 CI 流水线节奏。
JS 动态生成内容时最容易忽略的焦点与 ARIA 同步
很多团队重构完静态 HTML,一上交互就崩——因为 JS 插入的按钮没用 <button></button>,弹窗没管理焦点,实时区域没加 aria-live,辅助技术根本感知不到变化。
使用场景:模态框、通知提示、搜索建议、表单校验反馈等动态区域。
div 换成 <main></main>,而是让每个新写的 <section></section> 都自带 aria-labelledby、每个新插入的 <img alt="怎么避免在HTML工程化重构中破坏原有的可访问性" > 都强制校验 alt 是否非空——这些约束必须变成开发流程里的默认行为,而不是每次都要靠人想起来。



















