
本文详解如何在无法使用原生 ul/li 的场景下,通过 ARIA role="list" 和 role="listitem" 构建语义正确的嵌套列表,确保屏幕阅读器(如 NVDA)准确识别层级结构与项目数量。
本文详解如何在无法使用原生 ul/li 的场景下,通过 aria `role="list"` 和 `role="listitem"` 构建语义正确的嵌套列表,确保屏幕阅读器(如 nvda)准确识别层级结构与项目数量。
在构建无障碍 Web 应用时,若因样式、框架限制或组件封装等原因无法使用原生 <ul></ul>/<li>,而必须采用 <div> 元素模拟列表结构,则必须严格遵循 ARIA 语义规范——否则屏幕阅读器将无法正确解析嵌套关系与项目总数。<p>核心原则是:<strong><code>role="list" 的直接子元素必须全部为 role="listitem";任何嵌套子列表(role="list")必须置于 role="listitem" 内部,而非独立包裹在额外的容器 div 中。 这与 HTML 原生规则完全一致:<ul></ul> 中不可直接嵌套 <ul></ul>,必须通过 <li> 包裹子列表。
以下为错误写法(导致屏幕阅读器仅识别 2 个顶层项):
<div role="list">
<div> <!-- ❌ 无 role 的 div 破坏语义 -->
<div role="listitem">Item 1</div>
<div role="listitem">Item 2</div>
</div>
<div> <!-- ❌ 同上 -->
<div role="listitem">Item 3</div>
<div role="listitem">Item 4</div>
</div>
</div>
正确写法应完全映射原生嵌套结构:
<div role="list">
<div role="listitem">Vegetables
<div role="list">
<div role="listitem">Tomatoes</div>
<div role="listitem">Lettuce</div>
<div role="listitem">Broccoli</div>
</div>
</div>
<div role="listitem">Fruit</div>
<div role="listitem">Meat</div>
<div role="listitem">Bread</div>
</div>
✅ 此结构中:
- 外层
role="list"拥有 4 个直接子元素,且全部为role="listitem"→ 屏幕阅读器正确宣布“列表包含 4 项”; - 第一个
listitem内部嵌套了完整的role="list",形成语义化子列表 → NVDA 会自然停顿并宣布“子列表,共 3 项”,无需退出外层列表上下文; - 所有非
list/listitem的中间容器(如分组div)均被移除,避免干扰辅助技术的 DOM 遍历逻辑。
⚠️ 注意事项:
-
禁止使用
role="group"替代listitem:group不具备列表项语义,不会被计入列表计数; -
勿添加冗余 wrapper
div:任何未声明role或声明错误role的父级div都会中断 ARIA 列表树; -
务必提供可见文本内容:
listitem必须包含可读文本(或aria-label),空div role="listitem"将被跳过; - 测试建议:使用 NVDA + Firefox 或 VoiceOver + Safari 进行朗读验证,关注“列表 X 项”“子列表 Y 项”等层级提示是否连续出现。
总结:ARIA 列表的可访问性不取决于元素类型(div 或 ul),而取决于角色嵌套的严格合规性。只要忠实还原原生语义层级——即“list → listitem → (optional) list → listitem…”——即可让所有主流屏幕阅读器准确传达结构信息,实现与标准 HTML 列表完全一致的无障碍体验。










