代表的な面接トピック

フロントエンド面接:アクセシブルなHTML Popoverの設計

フロントエンド普通
Offer.cc 編集チーム公開日 更新日

質問

ボタンで開くノンモーダルなフィルターサーフェスを設計してください。外側のクリックやEscapeキーで閉じ、トリガーとサーフェスのアクセシビリティを維持し、複数のサーフェスが誤ってお互いを閉じないようにする必要があります。HTML Popover APIをどのように活用しますか。また、モーダルダイアログとの境界線はどこにありますか?

プロンプトと適用範囲

これはモダンWebプラットフォームとアクセシビリティのインタラクションに関する問題です。目標は、light-dismissをサポートしながらページを引き続き操作可能な状態に保つメニュー、フィルターパネル、またはヘルプサーフェスを作成することです。HTML Popover APIは、popoverpopovertarget、およびtop layerを使用して、ブラウザ管理の表示ライフサイクルを提供します。すべてのオーバーレイをネイティブAPIに移行しないでください。ポジショニング自体が主要な課題ではありません。

面接官がテストしていること

  • popover="auto"popover="manual"、およびモーダルダイアログのセマンティクスを区別できること。
  • トリガー、サーフェス、および閉じるアクションをセマンティックに理解しやすい状態に保つこと。
  • Escape、外側クリック、複数サーフェス、フォーカス復帰、およびライフサイクルイベントの処理。
  • プログレッシブエンハンスメント、ブラウザサポート、および非破壊的なフォールバックの説明。

最初に確認すべき明確化事項

背景が操作可能なままであるかを確認します。ブロックする必要がある場合は、popoverではなく<dialog>showModal()を使用します。同時に開けるサーフェスが1つだけか、複数のmanualサーフェスが共存できるか、トリガーが開閉を切り替えるか、フォーム送信の成功時に閉じるか、および最小限の対象ブラウザ範囲を明確にします。フィルターサーフェスは通常ノンモーダルですが、キーボードユーザーには明示的なフォーカスと閉じる導線が依然として必要です。

30秒の回答

セマンティックなHTMLから始めます。トリガーにpopovertargetを配置し、サーフェスにpopover="auto"を配置します。manualはカスタムの閉じる制御にのみ使用します。ブラウザがtop-layerの動作、Escape、および外側のlight-dismissを提供します。ここにフォーカスの移動、フォーカスの復帰、ステータステキスト、およびbeforetoggle/toggleライフサイクル処理を追加します。サポートされていないブラウザでは、無関係なサーフェスを閉じる可能性があるグローバルクリックハンドラーを使用する代わりに、小さなポリフィルを使用して同じDOMと名前を維持します。

ステップバイステップの解決策

1. popoverかモーダルダイアログかを選択する

Popoverはメニュー、フィルター、ブロックしないヘルプ、および一時的なアクションに適しており、ページはノンモーダルのままです。<dialog>showModal()の組み合わせは、モーダルセマンティクスを提供し背景をブロックするため、最初に処理しなければならない確認、支払い、警告に適しています。どちらもtop layerを使用できますが、アクセシブルな名前、フォーカス戦略、および閉じる理由は異なります。dialogをpopover属性に置き換えることは等価ではありません。

2. トリガーとサーフェスの関係を確立する

最もセマンティックな最小構造により、ブラウザがターゲット関係を維持できるようになります。

html
<button type="button" popovertarget="filters" aria-controls="filters">
  Filters
</button>
<div id="filters" popover="auto">
  <form method="get">
    <label>Status <select name="status"><option>All</option></select></label>
    <button type="submit">Apply</button>
  </form>
</div>

popovertargetによりボタンが宣言的トリガーとなり、ブラウザが開閉時の関係を管理します。aria-controlsは支援技術が関係を理解するのに役立ちますが、可視の名前や適切なフォーカス動作の代わりにはなりません。複雑なコンポーネントでは、スクリプト内でトリガーの参照を保持し、トリガーが依然として存在しフォーカス可能な場合にのみフォーカスを戻す必要があります。

3. auto、manual、およびlight-dismissを理解する

autoのpopoverは、ブラウザ管理のlight-dismissをサポートします。Escapeキーまたは外側のクリックで閉じることができ、自動クローズチェーンに参加します。manualのpopoverはこれらのメカニズムによって閉じられないため、showPopover()hidePopover()、またはtogglePopover()を明示的に呼び出します。複数のサーフェスが共存できる場合やライフサイクルを独自に管理する場合に使用します。autoの上にドキュメントレベルのクリッククローズを追加しないでください。ネストされたサーフェスやトリガーのクリックと競合する可能性があります。

4. top layer、ポジショニング、バックドロップを処理する

開くと、popoverはtop layerに入り、通常の祖先要素のoverflowz-indexスタッキングコンテキストによる制約を受けなくなります。配置にはCSS Anchor Positioningまたは通常のレイアウトを使用し、配置に失敗した場合の静的フォールバックを用意します。ノンモーダルpopoverに全画面のバックドロップを自動的に付与すべきではありません。プロダクトでバックドロップが必要な場合は、要件が実際にはモーダルダイアログの挙動ではないかを再確認してください。top layerは描画順序を変更しますが、狭いビューポート、スクロールコンテナ、または衝突時の配置問題を解決するわけではありません。

5. フォーカスとキーボードの挙動を定義する

フォーカスを最初のアクション可能なコントロールに移動するか、テストした上でブラウザのデフォルトのままにします。Escapeキーでフォーカスをトリガーに戻します。トリガーが削除された場合は、最も近い表示コンテキストにフォールバックします。Tabキーの順序はサーフェス内を自然に移動するようにし、tabindex="-1"ですべてのコントロールを非表示にしないでください。メニュー風のpopoverにはメニューアイテムの矢印キー操作と選択セマンティクスが必要ですが、フィルターフォームは通常のフォームのキーボード動作を維持する必要があります。

6. グローバル状態を推測するのではなくライフサイクルを監視する

beforetoggleを使用して遷移を検証し、閉じた理由を記録し、またはアプリケーションの状態を同期します。ラベルとフォーカスを更新するには、状態が実際に変化した後にtoggleを使用します。要素ごとにハンドラーを登録し、コンポーネントの破棄時にクリーンアップします。相互排他的なautoサーフェスの場合、古いグローバルなオープンIDを維持するのではなく、ブラウザのクローズスタックに依存します。

7. フォーム送信とアプリケーション状態を調整する

フィルターの送信が成功した後は、必要に応じてhidePopover()を明示的に呼び出しますが、閉じることを成功の証拠として扱わないでください。リクエストが失敗した場合はサーフェスを開いたままにし、エラーをフォームに関連付けます。開閉状態は一時的なUI状態であり、フィルター値はビジネス状態です。これらを分離して保持し、:popover-openを唯一の信頼できる情報源として扱うのではなく、ナビゲーションや履歴の変更時に再計算します。

8. プログレッシブエンハンスメントを使用し、インタラクションをテストする

HTMLElement.prototype.showPopoverなどの実際のAPIを機能検知します。利用可能な場合はネイティブの動作を使用し、そうでない場合は、表示、Escape、外側クリック、フォーカス復帰用の小さなポリフィルを使用して、同じトリガー、コンテンツ、アクセシブルな名前を維持します。キーボード、タッチ、ズーム、スクロール、ネストされたサーフェス、削除されたトリガー、フォームの失敗、および視覚効果の抑制(reduced motion)をテストします。ピクセルだけを確認するのではなく、スクリーンリーダーで名前、状態、エラーを検証します。

模範解答

これをダイアログではなく、ノンモーダルなフィルターpopoverとして定義します。ボタンはpopovertargetを使用し、パネルはpopover="auto"を使用します。ブラウザがtop layer、Escape、および外側のlight-dismissを管理し、スクリプトがフォーム状態、フォーカス復帰、ライフサイクルイベントを処理します。開くと、フォーカスが最初のコントロールに入り、閉じるとトリガーに戻ります。送信が失敗した場合はサーフェスを開いたままにしてエラーを表示し、成功した場合は閉じることができます。manualは複数のカスタムサーフェスが共存する必要がある場合にのみ使用し、autoに対してドキュメントのクリック処理を重複させないようにします。機能検知が失敗したときのための静的配置フォールバックとセマンティックなポリフィルを提供します。キーボード、タッチ、スクロール、および支援技術のパスをテストします。

よくある間違い

  • 背景をブロックする必要がある確認画面にpopoverを使用し、モーダルセマンティクスを失うこと。
  • autoにドキュメントクリックでの閉じる処理を追加し、ネストされたサーフェスや送信コントロールを閉じてしまうこと。
  • トリガーの関係、フォーカス復帰、またはエラー状態を維持せずにdisplayを切り替えること。
  • ノンモーダルの動作を維持しながら、全画面バックドロップでダイアログを模倣すること。
  • top layerを自動配置として扱い、スクロール、狭いビューポート、衝突フォールバックを無視すること。
  • 失敗したフォームを無条件に閉じてしまい、ユーザーがエラーを確認・修正できなくなること。
  • マウスクリックのみをテストし、Escape、Tab、タッチ、スクリーンリーダーの動作を見落とすこと。

フォローアップの質問

代わりに<dialog>を使用すべきなのはどのような場合ですか?

背景をinertにする必要がある場合、ユーザーが最初に決定を下す必要がある場合、またはモーダルフォーカス動作が必要な場合は、<dialog>showModal()を使用します。Popoverのlight-dismissとノンモーダルセマンティクスは、背景のブロックや明示的なキャンセル/確認アクションの代わりにはなりません。

複数のフィルターサーフェスを相互排他的にするにはどうすればよいですか?

相互排他的なサーフェスにはautoを優先し、ブラウザがクローズチェーンを管理できるようにします。複数のmanualサーフェスが共存できる場合は、明示的なインスタンスセットを保持し、対象のインスタンスのみを閉じ、各インスタンスのトリガーとフォーカス復帰を個別に保存します。

Popover APIのないブラウザをどのようにサポートしますか?

APIを機能検知し、表示、閉じる動作、Escape、外側クリック、フォーカス復帰のためのポリフィルを使用して、同じDOMとセマンティック属性を維持します。名前がなくキーボードでアクセスできない絶対配置のdivにデグレードさせないでください。互換性マトリクスをドキュメント化し、リリース前にテストします。

light-dismissの境界をどのようにテストしますか?

トリガー、サーフェス内、外側の兄弟要素、スクロールコンテナ、および別のpopoverでのクリックをテストします。内部のクリックでは開いたままである必要があり、外側のクリックは該当するautoサーフェスのみを閉じる必要があり、トリガーの連続クリックで重複した状態が作成されないようにします。Escapeおよびタッチのテストも追加します。

アニメーションがアクセシビリティを損なうことはありますか?

prefers-reduced-motionを使用して、フォーカスと読み取り可能な状態の一貫性を保ちながらトランジションを短縮または削除します。不透明度(opacity)だけでフォーカス可能なコントロールを非表示にしないでください。閉じる処理中の重複送信を防ぎ、支援技術に対して安定した状態変化を公開します。

配置に失敗した場合はどうなりますか?

アンカーポジショニングを使用する場合は、通常のフローまたはビューポートセーフなフォールバックを提供し、サイズやスクロールの変更に対応します。配置の失敗によってコンテンツがクリップされたり画面外に押し出されたりしてはなりません。配置拡張機能がなくても、コアとなるフォームは引き続き使用可能である必要があります。

公開情報ソース

関連する質問