错误添加aria属性反而破坏可访问性:role="tree"不可包裹非树结构,aria-expanded与aria-hidden不能共存,子节点须保留在dom中并同步更新aria-setsize等状态。

直接在复杂 HTML 树中加 aria- 属性,不等于可访问性生效;错误的属性组合、缺失的状态同步、或与语义标签冲突,反而会让屏幕阅读器读错甚至跳过内容。
别用 role="tree" 套整个 <div> 结构
<p>常见错误是把一整块菜单区域(含搜索框、操作栏、分页器)都包进 <code>role="tree"。这会强制辅助技术把所有子元素都当作树节点处理,导致按钮、输入框失去原生角色,Enter 键失效,Tab 导航混乱。
- 只对真正具备层级展开/折叠行为的列表结构用
role="tree",比如嵌套的 <ul></ul> + <li> 组合
- 外部容器(如包裹树的
<aside></aside> 或 <section></section>)应使用 role="region" 或 aria-labelledby 关联标题,而非强行塞进 tree 角色
- 若树内混有非树节点(如“刷新”按钮),必须显式设
role="none" 或 aria-hidden="true",否则会被误读为可交互树项
aria-expanded 和 aria-hidden 不能共存于同一元素
role="tree",比如嵌套的 <ul></ul> + <li> 组合<aside></aside> 或 <section></section>)应使用 role="region" 或 aria-labelledby 关联标题,而非强行塞进 tree 角色role="none" 或 aria-hidden="true",否则会被误读为可交互树项aria-expanded 和 aria-hidden 不能共存于同一元素看到有人给折叠项同时写 aria-expanded="false" aria-hidden="true"——这是典型误区。两者语义冲突:aria-expanded 表示“该节点有可展开内容”,而 aria-hidden="true" 告诉屏幕阅读器“彻底忽略它”。结果是:视觉上收起,但辅助技术既不读状态,也不读子内容,用户完全感知不到存在子节点。
- 控制显示/隐藏,优先用 CSS
display: none或visibility: hidden,再配合aria-expanded同步状态 - 子节点容器(如
<ul class="children"></ul>)应始终保留在 DOM 中,仅通过 CSS 控制显隐;不要用innerHTML = ""或remove()动态删节点,否则aria-posinset/aria-setsize计算会断掉 - 切换时,必须同步更新
aria-expanded和父节点的aria-controls指向(值要严格匹配子容器id)
动态渲染树时,tabindex 必须手动管理
框架(如 React/Vue)默认不处理键盘焦点链。树节点若没设 tabindex="-1",方向键导航就失效;设成 tabindex="0" 又会导致 Tab 键遍历所有节点,破坏单点聚焦逻辑。
- 初始渲染后,只给首个可见节点设
tabindex="0",其余同级节点设tabindex="-1" - 键盘操作(上下键移动、左右键展开)时,需用 JS 主动调用
.focus()并同步更新tabindex值,不能依赖 CSS:focus伪类 - 注意:
tabindex="-1"元素无法被 Tab 键选中,但可通过 JS 聚焦——这正是树形控件需要的行为 - 若树支持多选,还需维护
aria-selected状态,并响应Space键切换
最易被忽略的是:当树节点内容由异步数据填充(比如点击展开后才请求子项),aria-setsize 和 aria-posinset 的值必须等真实 DOM 渲染完成后再写入,否则屏幕阅读器会按旧数值朗读“第 1 条,共 0 条”。











