
本文详解如何通过 role="list" 和 role="listitem" 正确构建语义化、屏幕阅读器友好的嵌套列表结构,解决因角色嵌套错误导致读屏器误判列表项数量的问题。
本文详解如何通过 `role="list"` 和 `role="listitem"` 正确构建语义化、屏幕阅读器友好的嵌套列表结构,解决因角色嵌套错误导致读屏器误判列表项数量的问题。
在构建无障碍(Accessibility)Web 应用时,若因样式、布局或框架限制无法使用原生 <ul></ul>/<li>,而必须使用 <div> 模拟列表结构,仅添加 <code>role="list" 并不能自动识别嵌套项——关键在于 ARIA 角色的严格层级约束。
❌ 常见错误:无效嵌套破坏语义
原始代码的问题在于:
-
role="list"的直接子元素未全部声明为role="listitem"; - 子列表(
role="list")被错误地置于无语义的<div> 容器中,而非嵌套在 <code>role="listitem"内部; - 这导致屏幕阅读器(如 NVDA、JAWS)仅将两个
<div>(即外层 <code>role="list"的直接子节点)识别为列表项,完全忽略其内部的listitem。<!-- 错误示例:语义断裂 --> <div role="list"> <div> <!-- ❌ 无 role,非 listitem --> <div role="listitem"></div> <div role="listitem"></div> </div> <div> <!-- ❌ 同上 --> <div role="listitem"></div> <div role="listitem"></div> </div> </div>✅ 正确结构:严格遵循 ARIA 列表嵌套规范
根据 WAI-ARIA 1.2 规范,
role="list"的所有直接子元素必须是role="listitem";而嵌套子列表(role="list")必须作为listitem的子内容存在,不可“悬空”于任意容器中。这与 HTML 原生规则一致:<ul></ul>中只能直接包含<li>,子<ul></ul>必须置于<li>内部。正确写法如下:
<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>✅ 屏幕阅读器行为:NVDA 将准确播报“列表,4 项”,进入“Vegetables”项后,自动识别其内部为“列表,3 项”,并保持上下文连续性(不提示“退出列表”),清晰传达层级关系。
⚠️ 关键注意事项
-
禁止“裸 div”混入:
role="list"下不得存在无role="listitem"的<div>、<code><span></span>或文本节点(除非包裹在listitem内); -
子列表必须依附于 listitem:
role="list"不可直接嵌套于另一role="list",中间必须经由role="listitem"中转; -
避免滥用
role="group":group不具备列表语义,无法替代list/listitem,对屏幕阅读器列表计数无帮助; -
建议优先使用原生语义:若无强约束,始终首选
<ul><li></ul>—— 它们默认具备完整无障碍支持,且无需额外 ARIA 属性。
✅ 总结
ARIA 角色不是“魔法补丁”,而是对底层语义的显式声明。实现可访问的嵌套列表,核心在于精准复刻原生 HTML 的嵌套逻辑:
list → listitem → (optional) list → listitem…。严格遵循此结构,即可确保各类主流屏幕阅读器正确解析项数、层级与导航逻辑,真正达成“写一次,读全量”的无障碍目标。 -
禁止“裸 div”混入:










