必须为iframe显式设置有意义的title属性,子页面需自行完成可访问性初始化,微模块应避免多个<main>标签冲突,改用<section role="region">并确保动态加载后在下一渲染帧聚焦。

iframe内嵌页面无法被屏幕阅读器聚焦怎么办
根本原因不是iframe本身不可访问,而是父页面没告诉辅助技术“这里面有内容可读”。iframe默认是title缺失、aria-hidden="true"隐式生效的状态,NVDA和VoiceOver会直接跳过。
必须显式提供title属性,且内容需有意义——不能写“老系统模块”或空字符串。例如:<iframe src="legacy-report.html" title="销售报表查看模块"></iframe>。
如果iframe加载的是跨域页面(常见于微模块集成),title仍有效,但无法用JS控制其内部焦点。此时要靠子页面自己完成可访问性初始化:子页<body>加载后,立即执行document.getElementById("main-content")?.focus(),并确保该元素有tabindex="-1"。
- 禁止依赖父页面JS去
contentWindow操作子页面DOM——跨域报错,且AT不感知 - 子页面
<html lang="zh-CN">必须存在且正确,否则VoiceOver朗读会乱码 - 若子页面无
<main>或标题层级断裂,父页面加title也救不了结构语义
微模块间锚点跳转失效的底层原因
当点击<a href="#section-2">跳转</a>却没反应,大概率是目标模块还没挂载到DOM,或者锚点ID在动态渲染后才生成——Lighthouse检测不到,但屏幕阅读器用户按H键导航时直接“掉层”。
立即学习“前端免费学习笔记(深入)”;
解决方案不是加scrollIntoView(),而是让每个微模块暴露一个registerAnchor(id)方法,在挂载完成时主动注册自身支持的锚点ID列表。父容器统一监听hashchange,匹配后调用对应模块的scrollToAnchor(id)。
- 避免用
location.hash直接跳转,它不触发模块激活逻辑 - 每个模块的锚点ID必须全局唯一,推荐前缀化,如
report-section-2而非section-2 - 注册时机必须在
DOMContentLoaded之后、且<main>已插入DOM树中
多系统共存时<main>冲突怎么解
多个微模块都带<main>标签,浏览器只认第一个,其余全被忽略——这是结构性失效,不是样式问题。Lighthouse报document-has-main错误只是表象,本质是无障碍树只构建一次<main>节点。
标准解法:主容器保留唯一<main>,所有微模块内部改用<section role="region" aria-labelledby="module-title">,并在模块标题上设id="module-title"。这样既保持语义区域划分,又不破坏无障碍树根结构。
- 绝对禁止在微模块里写
<main><main>嵌套,NVDA会完全跳过整个区域 - 若模块需键盘导航入口,给
<section>加tabindex="0",而不是模拟<button> - React/Vue组件中动态渲染时,确保
role="region"与aria-labelledby绑定在真实DOM节点上,别用虚拟属性
动态加载模块后焦点丢失的修复时机
模块加载完成≠可访问就绪。常见现象:按钮已渲染,但Tab键按过去没反应;屏幕阅读器朗读完上一模块就停住,不自动进入新模块。
关键动作必须发生在模块DOM插入后的**下一个渲染帧**,用requestAnimationFrame(() => { element.focus(); }),而不是setTimeout或直接.focus()。因为AT需要等浏览器完成无障碍树重建才能同步状态。
- 焦点目标元素必须有
tabindex="0"或为原生可聚焦元素(<button>、<a href>) - 若模块含表单,首次聚焦应落在
<form>首个<input>,而非容器div - 不要在模块卸载时盲目
blur(),这会导致AT误判为页面结束
最易被忽略的点:微模块的可访问性不是“加几个ARIA属性”就能解决的,它要求每个模块独立满足lang、<title>、标题层级、焦点链完整这四条硬约束——父容器只能协调,不能代偿。



















