Popover API
z-indexの数値合戦もJSの開閉制御も手放せる。popover属性1つでトップレイヤー描画・ライトディスマス・フォーカス管理が標準搭載される仕組みを原理から解説します。
- 1.popover属性を付けた要素はデフォルトでdisplay:noneだが、表示時はDOM上の位置と無関係にトップレイヤーへ描画され、他の要素の上に必ず乗るため重なり順の悩みが消える。
- 2.popover=auto(既定)はライトディスマス(外側クリックやEscで自動的に閉じる)と直前フォーカスへの復帰を備え、popover=manualはこれらを備えない常時手動制御。
- 3.popovertarget/popovertargetactionでボタンから宣言的に開閉でき、beforetoggle/toggleイベントとCSSの:popover-open擬似クラスで状態に応じたスタイルや制御ができる。
popover属性が解く問題:重なり順と開閉制御の自前実装
ツールチップやメニュー、通知トーストのようなUIは、長らく position: fixed と高い z-index の組み合わせで実装されてきました。しかし祖先に contain や transform がかかっていると新しい包含ブロックやスタッキングコンテキストが生まれ、z-index をいくら上げても親の重なり順に閉じ込められて期待通り最前面に出ません(スタッキングコンテキストの生成条件は スタッキングコンテキストとz-indexの解決順 を参照)。さらに「外側クリックで閉じる」「Escで閉じる」「開いたら中にフォーカスを移し、閉じたら元の要素に戻す」はすべてJSで手作りする必要があり、実装のたびに漏れが生じがちでした。
Popover API はこれをHTML属性1つで解決します。任意の要素に popover 属性を付けるだけで、その要素はトップレイヤーという特別な描画層に載る資格を得て、開閉・ライトディスマス・フォーカス管理をブラウザが標準で面倒みます。
<button popovertarget="menu">メニュー</button>
<div id="menu" popover>
<p>ここがポップオーバーの中身</p>
</div>
popover 属性を持つ要素は、ユーザーエージェントスタイルシートで暗黙に display: none になっており、開かれるまで画面にもアクセシビリティツリーにも実質的な形では現れません。
トップレイヤーでの描画:DOM位置から解放される
要素が「開いた」状態になると、ブラウザはそれを通常のレンダリングツリーの位置から引き上げ、**トップレイヤー(top layer)**という文書ツリーの外側にある専用の描画層に配置します。トップレイヤーは <dialog> の showModal() で表示されるモーダルダイアログや全画面API(Fullscreen API)の要素と同じ場所で、常に文書内の他のどんなスタッキングコンテキストよりも上に描画される、という保証があります。
これが意味するのは、ポップオーバー要素がDOM上のどこに書かれていても——たとえ深くネストした overflow: hidden なコンテナの中にあっても——開いた瞬間にそのコンテナの制約から抜け出し、画面の最前面に描画されるということです。祖先の z-index や contain: layout paint によるクリッピングを気にする必要がありません。
トップレイヤーの各要素は、仕様上「文書のスタッキングコンテキストの、さらに上に位置する疑似要素の中に順番に積まれる」ものとしてモデル化されています。トップレイヤーに複数の要素(例: ダイアログとポップオーバー)が同時に存在する場合は、後から追加された方が上に来ます。同一トップレイヤー内での重なり順にも z-index を指定できますが、通常の文書のスタッキングとは独立した別枠の比較です。
popover 属性を持つ要素には :popover-open 擬似クラスが使え、開いている間だけスタイルを当てられます。加えてブラウザは開閉のたびに要素へフェードイン・フェードアウト用のデフォルトトランジション(display と overlay プロパティを含むトランジション遷移)を許すため、transition に display と overlay を加えるだけでネイティブな開閉アニメーションが書けます。
[popover] {
opacity: 0;
transition: opacity 0.2s, display 0.2s allow-discrete, overlay 0.2s allow-discrete;
}
[popover]:popover-open {
opacity: 1;
}
宣言的な開閉制御:popovertarget
ポップオーバーを開閉する最も簡単な方法は、<button> や <input type="submit"> に popovertarget 属性でターゲットのidを指定することです。これだけでJSを1行も書かずにトグル動作が実装されます。
<button popovertarget="tip" popovertargetaction="show">表示</button>
<button popovertarget="tip" popovertargetaction="hide">非表示</button>
<div id="tip" popover>補足情報です。</div>
popovertargetaction を省略すると既定値は toggle で、押すたびに開閉が切り替わります。show / hide を明示すれば一方向の制御になります。JSから操作したい場合は要素自身が持つ showPopover() / hidePopover() / togglePopover() メソッドを直接呼べます。
| API | 契機 | 用途 |
|---|---|---|
| popovertarget属性 | ボタンのクリック | 宣言的・JS不要の開閉 |
| showPopover() / hidePopover() | 任意のJSロジック | 条件付きの表示制御 |
| togglePopover(force) | 任意のJSロジック | force引数で強制的に開閉状態を指定 |
モーダル/非モーダルの違い:popover=auto と manual
popover 属性の値は auto(既定値、空文字列や属性名のみの指定も同義)と manual の2種類です。ここでいう「モーダル」は <dialog> の showModal() が作る完全なモーダル(背景操作を完全に無効化し ::backdrop を持つ)とは異なる概念で、Popover APIの文脈では自動で閉じる(light-dismissする)かどうかが本質的な違いになります。
| 値 | ライトディスマス | 自動排他 | 典型用途 |
|---|---|---|---|
| auto(既定) | 外側クリック・Escで自動的に閉じる | 他のauto系ポップオーバーが開くと自動で閉じる | メニュー・コンボボックス・ツールチップ |
| manual | 自動では閉じない。明示的なhidePopover等が必要 | 他のポップオーバーと独立して共存できる | 常時表示の通知トースト・複数同時表示のUI |
popover=auto の要素は、開くとポップオーバースタックという管理下に置かれます。新しいauto系ポップオーバーが開いたとき、それが既存の開いているポップオーバーの祖先や子孫でなければ、スタック上の関係ない要素は自動的に閉じられます。これにより「メニューを開いたら前のメニューが閉じる」という排他制御が、実装コード無しで実現されます。ライトディスマスの発火条件は、ポップオーバー本体の外側でのポインタ操作(pointerdown)と、Escape キー押下です。popover=manual はこの一連の自動処理を一切受けません。開いたままにしたい通知や、複数を同時表示したいUIに向きます。
ポップオーバー内部の要素をクリックしても閉じません。判定は「ポインタダウンの座標がポップオーバー自身、およびそれを開いた popovertarget ボタンの内側かどうか」で行われ、両方の外側であった場合にのみ閉じます。ネストしたポップオーバー(サブメニューなど)では、祖先・子孫関係にある限り連鎖的には閉じない設計です。
フォーカス管理:自動フォーカスと復帰
popover=auto の要素が開くと、ブラウザは自動フォーカスを試みます。要素内に autofocus 属性を持つ子孫があればそこへ、無ければポップオーバー要素自身(フォーカス可能でなくても一時的にフォーカスターゲットになれる)へフォーカスを移します。閉じるときは、開く直前にフォーカスされていた要素へ自動的に戻ります。この「開く前の状態への復帰」は <dialog> の showModal() と共通する設計で、キーボード操作やスクリーンリーダー利用時に「メニューを開いて閉じたら、元いたボタンに戻っている」という直感的な体験を保証します。
ただし、Popover APIの非モーダル的な性質上、フォーカストラップ(Tabキーでポップオーバー外に出られないようにする閉じ込め)はデフォルトで行われません。<dialog> の showModal() はモーダル状態でTab移動をダイアログ内に閉じ込めますが、popover=auto は開いていてもTabで背景要素へフォーカスが移動できます。厳密なモーダル的閉じ込めが必要な場面(フォーム内の重要な確認ダイアログなど)は <dialog> を使うべきで、Popover APIは「背景操作をブロックしないライトウェイトな浮遊UI」に向いた設計だと理解しておく必要があります。
頻出は、(1) popover 属性を持つ要素は開くとDOM上の位置に関係なくトップレイヤーへ描画され、祖先のスタッキングコンテキストや overflow の制約を受けない点、(2) popover=auto(既定)は外側クリック・Escによるライトディスマスと、他のauto系ポップオーバーを閉じる自動排他を持つが、popover=manual は持たない点、(3) 開いたときに autofocus 要素または自身へ自動フォーカスし、閉じると直前の要素へ復帰する点、(4) それでも <dialog> の showModal() のようなフォーカストラップは行われないため、真のモーダルが必要ならdialogを使うべき点、の4点です。「popoverは常にモーダル的にTabを閉じ込める」は典型的な誤りです。
まとめ
Popover APIは、popover 属性を付けるだけで要素をトップレイヤーの描画対象にし、DOM上の位置や祖先の z-index・contain に関係なく最前面に表示します。popovertarget / popovertargetaction で宣言的に開閉でき、JSからは showPopover() / hidePopover() / togglePopover() が使えます。既定の popover=auto は外側クリックやEscで閉じるライトディスマスと、他のauto系ポップオーバーを自動で閉じる排他制御を備え、popover=manual はこれらを持たない常時手動の表示に向きます。開閉に伴い自動フォーカス・直前要素への復帰が働きますが、Tabキーのフォーカストラップは行われないため、真に背景操作を封じるモーダルには引き続き <dialog> の showModal() を使い分けます。トップレイヤーと重なり順の基礎は スタッキングコンテキストとz-indexの解決順、宣言的なUI部品としての設計思想は カスタム要素のライフサイクルとアップグレード、フォーカス移動やキーボード操作の基盤は アクセシビリティ と合わせて押さえると理解が深まります。
Web/フロントエンド Article
Popover APIを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Popover API
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
popover=auto(既定)はライトディスマス(外側クリックやEscで自動的に閉じる)と直前フォーカスへの復帰を備え、popover=manualはこれらを備えない常時手動制御。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「Popover API / トップレイヤー」に近いか確認する。
- 強みである「popover属性を付けた要素はデフォルトでdisplay:noneだが、表示時はDOM上の位置と無関係にトップレイヤーへ描画され、他の要素の上に必ず乗るため重なり順の悩みが消える。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。