空状态占位符必须通过语义化容器(如)配合aria-busy="true"、role="status"等属性传达意图,不可仅用包裹;需绑定到具体业务区域,同步更新dom与语义,确保可访问性。

空状态占位符本身没有语义,<div> 或 <code><span></span> 不能表达“数据还没来”这个含义;必须靠结构+属性协同传达意图。
空状态不能只用 <div> 包裹 loading 文字
<p>常见错误是写成:<code><div class="loading">加载中…</div>。这在屏幕阅读器里读作“加载中”,但无法说明它属于哪个数据区块、是否可交互、是否正在替代某段真实内容。
- 缺失上下文:没有父级
<article></article> 或 <section></section>,机器无法判断该占位符对应哪块业务区域
- 缺乏状态标记:没用
aria-busy="true" 或 aria-live="polite",辅助技术不会主动播报变化
- 视觉与语义脱节:CSS 隐藏后,DOM 仍存在,但语义未同步更新(比如没移除旧内容或设
aria-hidden="true")
<main></main> 内部的空状态必须绑定到具体语义容器
<article></article> 或 <section></section>,机器无法判断该占位符对应哪块业务区域aria-busy="true" 或 aria-live="polite",辅助技术不会主动播报变化aria-hidden="true")<main></main> 内部的空状态必须绑定到具体语义容器比如博客列表页加载中,不能把占位符丢进 <main></main> 顶层,而应嵌套在它本该出现的位置——即未来要插入 <article></article> 的地方:
<main><section aria-busy="true" aria-live="polite"><h2>最新文章</h2>
<div class="placeholder" role="status">正在加载文章列表…</div>
</section></main>
这里 <section></section> 是逻辑分组单元,aria-busy="true" 告诉辅助技术该区域暂不可操作,role="status" 让屏幕阅读器以“状态更新”方式播报。
-
<section></section>比<div> 更准确:它表示一组相关的内容,而非无意义容器 <li>不要用 <code><article></article>包裹占位符:空状态不是一篇独立内容,强行套用会误导 SEO 和阅读器 - 避免在
<header></header>或<footer></footer>里放异步占位符:它们不承载主体数据流 - 替换前:给占位符加
aria-hidden="true",再移除节点(或用innerHTML = ''清空) - 插入后:确保新
<article></article>具备完整语义(如含<time datetime="2026-06-07"></time>) - 不要仅靠 CSS
display: none隐藏占位符:它仍存在于可访问树中,可能被读出 - 若用
fetch+then渲染,务必在catch分支也提供语义化错误占位:<div role="alert">加载失败,请重试</div>
aria-hidden 和 DOM 替换时机决定语义一致性
真实数据渲染完成后,必须同步清理占位符并恢复语义完整性。否则会出现“DOM 已更新但旧占位符仍挂载”的情况,导致双重播报或焦点陷阱。
最易被忽略的是:空状态不是“视觉补丁”,而是数据流中一个有明确角色的环节——它得知道自己替谁、等什么、失败后怎么交代。漏掉 aria-busy 或错放容器层级,会让整个异步流程在语义层面失效。











