
本文介绍如何在 Angular Material 中正确构建支持键盘导航和多级选择的嵌套列表,避开 mat-selection-list 嵌套导致的焦点与可访问性问题,改用 mat-list + 手动管理 mat-checkbox 实现语义清晰、符合 WCAG 标准的交互体验。
本文介绍如何在 angular material 中正确构建支持键盘导航和多级选择的嵌套列表,避开 `mat-selection-list` 嵌套导致的焦点与可访问性问题,改用 `mat-list` + 手动管理 `mat-checkbox` 实现语义清晰、符合 wcag 标准的交互体验。
Angular Material 官方组件库中,mat-selection-list 专为扁平化、单层可选列表设计,其内部已封装完整的键盘导航(如方向键切换、空格键选中)、ARIA 属性及焦点管理逻辑。但不支持嵌套
✅ 正确解法:放弃嵌套 mat-selection-list,转而使用语义化更强、更可控的组合方案:
- 外层使用
(纯容器,无内置选择逻辑); - 每个条目(含父项与子项)显式放置
; - 通过数据模型(如 item.checked)手动维护选中状态;
- 利用 @ViewChildren(MatCheckbox) + ngAfterViewChecked 确保初始焦点可达(提升可访问性)。
以下为推荐实现(已适配 Angular 17+ 的 @for 语法,并增强可维护性):
<mat-list>
@for (item of items; track item.id; let outerIndex = $index) {
<mat-list-item class="parent-item"><mat-checkbox item.id>
{{ item.name }}
</mat-checkbox></mat-list-item>
@if (item.subItems?.length) {
<mat-list class="nested-list" style="margin-left: 32px;">
@for (subItem of item.subItems; track subItem.id; let innerIndex = $index) {
<mat-list-item class="child-item"><mat-checkbox subitem.id item>
{{ subItem.name }}
</mat-checkbox></mat-list-item>
}
</mat-list>
}
}
</mat-list>
对应 TypeScript 控制逻辑(含父子联动逻辑示例):
export class NestedListExample {
items = [
{
id: 1,
name: 'Electronics',
checked: false,
subItems: [
{ id: 101, name: 'Laptops', checked: false },
{ id: 102, name: 'Phones', checked: false },
],
},
{
id: 2,
name: 'Clothing',
checked: false,
subItems: [
{ id: 201, name: 'Jackets', checked: false },
{ id: 202, name: 'T-Shirts', checked: false },
],
},
];
toggleParent(parent: any): void {
const allSubsChecked = parent.subItems?.every((s: any) => s.checked);
const someSubsChecked = parent.subItems?.some((s: any) => s.checked);
// 父项选中态:全选 → true;全未选 → false;部分选中 → indeterminate(需额外处理)
parent.checked = !parent.checked || (allSubsChecked && !someSubsChecked);
// 同步子项(可选:点击父项时全选/全取消)
if (parent.subItems) {
parent.subItems.forEach((sub: any) => sub.checked = parent.checked);
}
}
toggleChild(child: any, parent: any): void {
child.checked = !child.checked;
// 自动更新父项 indeterminate 状态(需配合模板中 [indeterminate] 绑定)
const checkedCount = parent.subItems.filter((s: any) => s.checked).length;
parent.checked = checkedCount === parent.subItems.length;
}
}
? 关键注意事项:
- ✅ 使用 mat-list-item 包裹 mat-checkbox 保证正确间距与垂直对齐;
- ✅ 为每个 checkbox 设置唯一 id 和 name,提升表单可访问性(支持 label[for] 关联);
- ✅ 子列表缩进使用 style="margin-left: 32px" 或 CSS 类(推荐 .nested-list { margin-inline-start: 32px; }),避免破坏 mat-list 的弹性布局;
- ⚠️ 若需「半选」(indeterminate)状态,请在 mat-checkbox 上添加 [indeterminate]="parent.indeterminate" 并在 toggleChild() 中计算;
- ? 键盘导航天然生效:Tab 进入,方向键在同级 checkbox 间移动,空格切换 —— 因 mat-checkbox 是原生可聚焦控件,且 mat-list-item 提供了正确的 role="listitem" 语义。
该方案不仅解决了原始嵌套失效问题,还赋予你完全的控制权:支持自定义父子联动逻辑、服务端同步、批量操作、动态加载子项等高级场景,是构建企业级可访问嵌套选择器的稳健实践。











