You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular单选按钮按向下箭头时焦点丢失的无障碍问题求助

Angular 组件无障碍焦点丢失问题解决方案

问题根源

  1. 单选按钮组未通过name属性关联,导致键盘上下箭头无法识别组内导航逻辑
  2. 模板切换(*ngIf 销毁重建DOM)后无焦点管理逻辑,焦点被重置到文档根节点
  3. 自定义单选按钮组件未正确映射原生radio的无障碍属性,导致键盘交互异常

修复步骤

1. 完善单选按钮组的无障碍结构

修改main-component模板,确保单选按钮属于同一组,并通过双向绑定简化选中状态管理:

<div>
  <form>
    <label id="template-select-label">选择模板</label>
    <custom-radio-button 
      name="template-group"
      [value]="'A'" 
      [(ngModel)]="selection"
    >A</custom-radio-button>
    <custom-radio-button 
      name="template-group"
      [value]="'B'" 
      [(ngModel)]="selection"
    >B</custom-radio-button>
    <custom-radio-button 
      name="template-group"
      [value]="'C'" 
      [(ngModel)]="selection"
    >C</custom-radio-button>
    <custom-radio-button 
      name="template-group"
      [value]="'D'" 
      [(ngModel)]="selection"
    >D</custom-radio-button>
  </form>

  <!-- 给每个模板容器添加tabindex="-1",用于切换后接收焦点 -->
  <div *ngIf="selection === 'A'" tabindex="-1" #templateContainer>A模板内容</div>
  <div *ngIf="selection === 'B'" tabindex="-1" #templateContainer>B模板内容</div>
  <div *ngIf="selection === 'C'" tabindex="-1" #templateContainer>C模板内容</div>
  <div *ngIf="selection === 'D'" tabindex="-1" #templateContainer>D模板内容</div>
</div>

2. 实现自定义单选按钮的无障碍封装

custom-radio-button组件必须完整映射原生radio的属性与行为,确保键盘导航和屏幕阅读器识别:

// custom-radio-button.component.ts
import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'custom-radio-button',
  template: `
    <label class="radio-label">
      <input 
        type="radio" 
        [name]="name"
        [value]="value"
        [checked]="innerValue === value"
        (change)="onChange($event.target.value)"
        (blur)="onTouched()"
      >
      <ng-content></ng-content>
    </label>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomRadioButtonComponent),
      multi: true
    }
  ]
})
export class CustomRadioButtonComponent implements ControlValueAccessor {
  @Input() name: string;
  @Input() value: any;

  innerValue: any;

  onChange: (value: any) => void = () => {};
  onTouched: () => void = () => {};

  writeValue(value: any): void {
    this.innerValue = value;
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
}

3. 添加模板切换后的焦点管理逻辑

在main-component.ts中监听选中状态变化,切换完成后将焦点设置到对应模板容器:

// main-component.ts
import { Component, ViewChildren, QueryList, ElementRef, AfterViewChecked } from '@angular/core';

@Component({
  selector: 'app-main',
  templateUrl: './main-component.html'
})
export class MainComponent implements AfterViewChecked {
  selection: string = 'A';
  @ViewChildren('templateContainer') templateContainers!: QueryList<ElementRef>;
  private prevSelection: string = '';

  ngAfterViewChecked(): void {
    if (this.selection !== this.prevSelection) {
      const activeContainer = this.templateContainers.find(container => 
        container.nativeElement.offsetParent !== null
      );
      if (activeContainer) {
        activeContainer.nativeElement.focus();
      }
      this.prevSelection = this.selection;
    }
  }
}

关键注意事项

  • 同一组单选按钮必须共享name属性,这是原生键盘上下箭头导航的核心前提
  • tabindex="-1"允许容器通过脚本接收焦点,但不会被Tab键主动选中,符合无障碍规范
  • 自定义组件实现ControlValueAccessor,确保与Angular表单体系兼容,同时保留原生radio的无障碍特性

内容的提问来源于stack exchange,提问作者coder_16

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:24:57