aria-posinset 和 aria-setsize 是屏幕阅读器定位列表项所必需的核心语义属性,必须成对使用且满足 1 ≤ aria-posinset ≤ aria-setsize;在自定义列表、虚拟滚动、树形结构、分页或动态筛选等场景中须手动精确设置,否则用户将丧失位置上下文。

aria-posinset 和 aria-setsize 不是可选的“锦上添花”,而是屏幕阅读器用户定位列表项时依赖的核心语义。如果你的列表是动态加载、虚拟滚动或树形结构,漏掉这两个属性,用户会完全失去上下文——比如听到“第 1 项”却不知道总共多少项,或在展开子节点后位置序号突然错乱。
什么时候必须手动设置 aria-posinset 和 aria-setsize
原生 <ol> 自动提供序号语义,但绝大多数自定义列表(React Window、ng-select、手写虚拟滚动、树形组件)不生成语义化序号,辅助技术无法推断位置。此时必须显式设置:
- 列表项角色为
role="option"、role="treeitem"或role="listitem"(且父容器未用语义化<ol>)时 - 集合总数动态变化(如搜索过滤后只剩 3 项,但原始数据有 100 项)——
aria-setsize应反映当前可见/可用集合大小,不是原始数据量 - 列表分页或懒加载:每页重置
aria-posinset起始值(如第 2 页首项应为aria-posinset="26",而非"1") - 树形结构中,
aria-setsize指的是**同级兄弟节点数**,不是整个树的节点总数;aria-posinset是当前节点在这些兄弟中的顺序
aria-posinset 和 aria-setsize 的取值逻辑
二者必须成对出现,且满足:1 ≤ aria-posinset ≤ aria-setsize。常见错误是硬编码或计算错范围:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
-
aria-posinset从 1 开始计数,不是 0 —— 即使 JS 数组索引是 0,这里也必须 +1 -
aria-setsize是当前层级/当前筛选结果下的总项数,例如:ng-select多选下拉中,若显示 8 个可选项,就设aria-setsize="8";若用户已选 2 项且启用“已选项置顶”,则新渲染的可选项只剩 6 个,aria-setsize就得同步改为"6" - 虚拟滚动场景(如 React Window 的
List):框架通常自动注入这两个属性,但前提是你的itemData数组真实反映当前视图集合——如果做了前端过滤,必须传入过滤后数组,不能传原始长数组 - 树节点展开/折叠时:
aria-setsize需实时更新为当前展开状态下的同级子节点数;若子节点异步加载,初始可设aria-setsize="0",加载完成再 patch
容易被忽略的兼容性与陷阱
这两个属性看似简单,但实际生效高度依赖角色(role)和父容器结构:
立即学习“前端免费学习笔记(深入)”;
- 仅当元素有明确集合角色(如
role="listbox"、role="tree"、role="grid")时,aria-posinset/aria-setsize才会被主流屏幕阅读器(NVDA、JAWS、VoiceOver)识别。单独加在div上无效 -
role="list"容器下,子项用role="listitem"时,这两个属性可选(因语义较弱),但若子项是role="option",就必须配对出现,否则 VoiceOver 可能静默跳过 - 避免与原生
<ol>混用:如果外层用了<ol>,内部再手动加aria-posinset属于冗余,还可能引发读屏重复播报 - React 中使用
aria-posinset={index + 1}时,确保index是当前渲染批次内的局部索引,不是全局 ID 或数据库主键
最常出问题的地方不在计算逻辑,而在于「谁该负责更新」:后端分页接口返回的 total_count 未必等于前端当前集合大小(比如加了客户端过滤),aria-setsize 必须忠实反映屏幕阅读器用户此刻看到的选项总数——哪怕这个数只在 JS 内存里存在,也得让它出现在 DOM 上。


















