Blocked aria-hidden の警告は正しく、直し方が間違っている | GH Media
URLがコピーされました

Blocked aria-hidden の警告は正しく、直し方が間違っている

URLがコピーされました
Blocked aria-hidden の警告は正しく、直し方が間違っている

モーダルを閉じた瞬間、コンソールに赤い警告が出る。「Blocked aria-hidden on an element because its descendant retained focus.」。見た目は何も壊れていないし、マウスでもキーボードでも普通に操作できる。だから優先度が下がり、そのまま残る。

この警告が厄介なのは、検索して出てくる対処法をそのまま当てると、警告は消えるのに問題は悪化する点です。しかも悪化したことは、マウスで動作確認している限り絶対に気づけません。UI コンポーネントのリポジトリでこの issue が繰り返し立ち続けているのは、そのためです。

ブラウザは正しいことを言っている

まず、この警告はライブラリのバグでもブラウザの過剰反応でもありません。順序のバグを正確に指摘しています。

起きているのはこういう状態です。モーダルの中にフォーカスがある。その状態で、モーダル(またはその祖先)に aria-hidden="true" が付く。すると支援技術からは見えない領域の中に、フォーカスされた要素が取り残されます。

スクリーンリーダー利用者から見ると、フォーカスは確かにどこかにあるのに、そこに何があるかは読み上げられない。操作できるのに存在しないものができあがります。ブラウザはこれを不整合と判断して aria-hidden の適用自体を拒否し、その事実を警告として出しています。つまり警告が出ている間、aria-hidden は効いていません。

原因は「隠す」処理と「フォーカスを外す」処理の順番だけです。隠す前にフォーカスを外に出していれば、この状態は発生しません。

見つかる直し方が、なぜどれも間違っているのか

出回っている対処は4種類にまとまります。全部、警告は消えます。

**document.activeElement.blur() を呼ぶ。**フォーカスをどこにも渡さずに外すため、フォーカスは body に落ちます。スクリーンリーダー利用者はページの先頭に放り出され、いま開いていたモーダルを何のために開いたのかという文脈を失います。キーボード利用者も、次に Tab を押した行き先が元の場所ではなくなります。

**setTimeoutaria-hidden の適用を遅らせる。**フォーカスが移動するまで待つという発想ですが、待っているのは実際には「たぶんそのくらいで終わるだろう」という時間です。端末が遅ければ間に合わず、警告は再発します。順序の問題を確率の問題に変えているだけです。

**aria-hidden を外す。**背景のコンテンツが支援技術から読める状態に戻るため、モーダルの外にも読み上げが漏れます。モーダルが「前面にある」という情報そのものが消えるので、警告の原因ごと機能を捨てていることになります。

**フォーカストラップを無効化する(modal={false} 相当)。**同様に、モーダルの外へ Tab が抜けていくようになります。閉じるボタンにたどり着けないまま背景をさまよう状態が起きます。

共通しているのは、フォーカスの行き先を決めないまま辻褄だけ合わせていることです。アクセシビリティ対応が法令上どこまで求められるかという議論以前に、これは機能の欠落として残ります。前提の整理はWebアクセシビリティ義務化の誤解にまとめています。

フォーカスがモーダル内に残ったまま aria-hidden を適用して警告が出る流れと、閉じる前にフォーカスを起点のボタンへ戻してから inert を適用する正しい順序を並べた比較図

順序として直す

直し方は、閉じる処理の順番を組み替えるだけです。

  1. **背景に付けている inert を先に外す。**フォーカスを戻す先が不活性のままだと、focus() が効きません
  2. **モーダルを開いた起点の要素へ、同期的に focus() を呼ぶ。**開くときに triggerRef として保持しておきます。ここが「フォーカスの行き先を決める」部分です
  3. 閉じていくモーダル側に inertpointer-events: none を付ける。aria-hidden ではなく inert を使います
  4. **transitionend または transitioncancel が発火してからアンマウントする。**時間ではなくイベントで待ちます
function closeModal() {
  backdrop.removeAttribute('inert');   // 1. 戻す先を先に活かす
  triggerRef.focus();                  // 2. 起点へ同期的に戻す
  panel.setAttribute('inert', '');     // 3. 閉じる側を不活性にする
  panel.style.pointerEvents = 'none';
  panel.addEventListener('transitionend', unmount, { once: true });
  panel.addEventListener('transitioncancel', unmount, { once: true });
}

3番目で inert を選ぶ理由は、aria-hidden が「支援技術から隠す」だけなのに対し、inertフォーカスの受け取り自体を止めるからです。aria-hidden は隠すのにフォーカスは通してしまうため、今回の不整合が構造的に起きえます。inert ならその組み合わせが成立しません。警告への対処というより、最初から inert を使うべき場面だった、というのが実態です。

なお、モーダルを <dialog> + showModal() で実装している場合、背景の不活性化とフォーカストラップはブラウザ側が持ちます。自前のフォーカス管理コードを丸ごと落とせるため、既存実装を触るならこちらへの置き換えも同時に検討する価値があります。詳細はdialog 要素と closedby 属性で扱っています。

マウスでは検出できないので、確認手順を変える

この種の不具合は、マウスを使った動作確認では100%見逃します。確認手順を分けておく必要があります。

  • モーダルを開く操作をキーボードだけで行い、Esc で閉じたあとに Tab を1回押す。フォーカスが開いたボタンの次に来るなら正しい
  • 閉じたあとにページ先頭へ飛ぶ、あるいはどこにフォーカスがあるか分からなくなるなら、blur() 型の対処が入っている
  • アニメーション中に連続で開閉して、警告が再発しないか見る。setTimeout 型の対処はここで露呈する

そもそもモーダルにすべきでない画面を無理にモーダルにしていることも多く、その場合はフォーカス管理を作り込むより画面を分けたほうが早い。判断軸はモーダルと別ページの使い分けにまとめています。

次にやること

コンソールにこの警告が出ているなら、すでに入っている「対処」を先に探してください。blur() の単独呼び出し、閉じる処理の setTimeout、コンポーネントに渡している aria-hidden の打ち消し。どれかが入っているなら、それを消すところが起点になります。警告が出ていない代わりに悪化している、という状態がありえます。

そのうえで、閉じる処理を「背景を活かす → 起点に戻す → 閉じる側を inert にする」の順に組み替える。既存のアニメーションはそのまま使えます。

既存サイトのアクセシビリティ改修や、モーダル周りのコンポーネント標準化を含めて相談したい場合は、グリームハブの開発・AI・自動化のご相談で承っています。実装の構成によって修正範囲は変わるため、個別にご相談ください。お問い合わせからどうぞ。

Sources

無料ダウンロード

Web制作 費用・発注・集客 完全ガイド【2026年版】

費用相場・制作会社の選び方・集客戦略まで、中小企業のWeb担当者が知っておくべき全知識をPDFにまとめました。

メルマガにも登録されます。いつでも解除可能です。

URLがコピーされました

グリームハブ株式会社は、変化の激しい時代において、アイデアを形にし、人がもっと自由に、もっと創造的に生きられる世界を目指しています。

記事を書いた人

鈴木 翔

鈴木 翔

技術の可能性に魅了され、学生時代からプログラミングとデジタルアートの分野に深い関心を持つ

関連記事

「WEB」の記事一覧を見る