lang属性必须在模板编译前写死并校验白名单,必须是的直接子元素,禁止嵌套于任何wrapper内,否则破坏语义链、导致屏幕阅读器忽略;标题层级须动态控制,避免跳级。

lang 属性必须在模板编译前就写死,不能靠运行时 JS 注入;<main></main> 必须是 的直接子元素,任何模板层的嵌套 wrapper 都会破坏语义链。
模板引擎里怎么安全注入 lang 值
很多模板(如 EJS、Nunjucks、Handlebars)允许用变量插值写 lang="{{ locale }}",但这是危险操作——如果 locale 是用户可控输入(比如 URL 参数或 cookie),可能注入 lang="zh-CN onerror=alert(1)",导致 XSS 或 AT 解析失败。
- 只接受白名单值:
zh-CN、zh-TW、en-US等 IANA 注册码,拒绝zh、cn、空字符串 - 服务端做校验,不是前端 JS 过滤;Node.js 中可用
new Intl.Locale(locale).toString()验证合法性 - SSR 场景下,
必须顶格输出,前面不能有 BOM、空格或注释,否则 NVDA 会跳过整个文档
<main></main> 在模板分块结构中如何不被“包坏”
常见错误:把 <main></main> 套进 <div class="container"> 或 <code><layout></layout> 组件里。即使模板语法看起来干净,编译后 DOM 就变成 <div><main>...</main></div>,屏幕阅读器直接无视 <main></main>。
- 模板入口文件(如
index.html或app.njk)中,<main></main>必须紧贴开始标签之后,中间无其他标签 - 布局复用用
<include></include>或<partial></partial>,而不是包裹式组件;内容区用{{ content }}直接插入到<main></main>内部 - 若必须加外层容器(如全站统一 padding),用
<div role="region" aria-label="主内容"> 替代,不破坏 <code><main></main>的直系关系标题层级断裂在模板循环中怎么防
模板里常这样写:
{% for post in posts %}<h2>{{ post.title }}</h2>...{% endfor %}。但如果页面已有<h1></h1>,这些<h2></h2>没问题;可一旦模板被复用到二级页(<h1></h1>缺失),所有<h2></h2>就变成“从 H2 开始”,跳过 H1,违反 WCAG 1.3.1。- 强制模板传入
headingLevel变量,动态生成<h headinglevel></h>;根页传 1,子页传 2 - 禁用硬编码
<h3></h3>,除非明确知道父级一定是<h2></h2> - 用 axe-core 在 CI 中扫描
heading-order规则,比人工 review 更可靠
真正难的不是写对一行
lang="zh-CN",而是确保模板继承链里每层都不悄悄加 wrapper、不默认降级标题、不把 locale 当字符串拼接——可访问性在模板层就断掉,后面所有 JS 补救都是徒劳。 - 强制模板传入











