モーダル利用時のaria-hiddenによるフォーカス警告の解決方法
問題の説明
Angularアプリケーションでモーダルダイアログ(ng-bootstrap)使用時に、次の警告が発生します:
Blocked aria-hidden on an element because its descendant retained focus.
The focus must not be hidden from assistive technology users.
Avoid using aria-hidden on a focused element or its ancestor.
Consider using the inert attribute instead.
根本原因:
モーダル表示時に以下の状況が同時に発生しています:
- モーダルを開くボタンがフォーカスを保持したまま
- 祖先要素に
aria-hidden="true"
が追加される - フォーカスされた要素が非表示領域内に残る
この組み合わせがアクセシビリティ上の衝突を引き起こし、スクリーンリーダー利用者がフォーカスを見失うリスクがあります。
推奨解決方法:フォーカスの明示的解除
サービスの拡張 (大規模プロジェクト向け)
NgbModal
を拡張したカスタムサービスを作成し、モーダル表示時に自動でフォーカスを解除します:
typescript
// custom-modal.service.ts
import { Injectable } from '@angular/core';
import { NgbModal, NgbModalOptions, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
@Injectable({ providedIn: 'root' })
export class CustomModalService extends NgbModal {
override open(content: unknown, options?: NgbModalOptions): NgbModalRef {
const activeElement = document.activeElement as HTMLElement;
activeElement?.blur(); // アクティブ要素からフォーカスを解除
return super.open(content, options); // 通常処理を継続
}
}
app.module.tsでの登録:
typescript
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { CustomModalService } from './custom-modal.service';
@NgModule({
providers: [{ provide: NgbModal, useClass: CustomModalService }]
})
export class AppModule {}
コンポーネント内での直接処理 (小規模プロジェクト向け)
typescript
openModal() {
const buttonElement = document.activeElement as HTMLElement;
buttonElement?.blur(); // フォーカス解除
this.modalService.open(ModalComponent, config);
}
動作の仕組み
document.activeElement
で現在フォーカス中の要素取得blur()
で明示的にフォーカス解除- モーダルを表示(解除後に
aria-hidden
が適用される)
代替解決方法
ディレクティブを使用した汎用的な対応
クリック時に自動的にフォーカスを解除するディレクティブ:
typescript
// blur-on-click.directive.ts
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[appBlurOnClick]',
standalone: true
})
export class BlurOnClickDirective {
constructor(private el: ElementRef) {}
@HostListener('click')
onClick(): void {
this.el.nativeElement.blur(); // クリック直後にフォーカス解除
}
}
テンプレートでの使用方法:
html
<button
appBlurOnClick
(click)="openModal()">
Open Modal
</button>
イベントリスナーを使ったグローバル対応
個別コンポーネント修正が難しい場合の方法:
typescript
document.addEventListener('hide.bs.modal', () => {
if (document.activeElement) {
(document.activeElement as HTMLElement).blur();
}
});
非推奨な回避策
避けるべき方法
aria-hidden
属性の単純削除:
diff
- <span aria-hidden="true">×</span>
+ <span>×</span>
この方法は警告を抑制しますが、スクリーンリーダーが不要な要素を読み上げるため、アクセシビリティを低下させます。
inert
属性の適用:
html
<div inert>...</div>
理論的には理想的な解決策ですが、2025年7月現在ではブラウザ実装が不安定なため推奨できません。
ベストプラクティスまとめ
- モーダル開閉時のフォーカス管理を常に意識
- フォーカス解除は
blur()
で明示的に実行 - 大規模プロジェクトではカスタムサービスの採用
- アクセシビリティチェッカーでの定期検証
inert
属性はブラウャサポート安定後に移行
モーダル実装時は必ずフォーカスの初期位置をモーダル内に設定し、閉じた後に適切な要素へフォーカスを戻すことも重要です。