結論
メニューやヒント、簡単なポップアップを新しく作るなら、まず popover 属性を選んでください。開閉、背景クリックでの自動クローズ(軽い閉じ)、ESCキーでの閉じ、そして他の要素の上に確実に重なるトップレイヤー表示が、JavaScriptをほとんど書かずに手に入ります。ここは自作だと地味に面倒で、とくに「z-indexを上げたのに親の overflow: hidden で切れる」といったスタッキングの問題を、Popover APIは構造的に回避します。
それでも自作を選ぶ場面は残ります。開閉アニメーションを細かく作り込みたいとき、あるいは既存の状態管理やデザイン基盤に完全に組み込みたいときです。この記事では両方を動くデモで並べ、宣言的に書ける範囲と、位置合わせをどうするかまで見たうえで、業務アプリでの線引きを示します。
迷ったら popover。自作へ倒すのは「凝った演出や既存基盤への統合が要る」と言い切れるときだけにする。
両方の動くデモ
下のボタンでそれぞれポップオーバーを開けます。開いた状態で ポップオーバーの外側をクリックしたり、ESCキーを押したりして、閉じ方の違いを試してみてください。標準機能版はここを自前で書いていません。
- popovertarget で開く(JS不要)
- 外側クリック・ESCで自動クローズ
- トップレイヤーに表示される
- クラス付け替えで開く
- 外側クリック・ESCは自前で実装
- 位置とz-indexは自分で管理
標準機能版はボタンに popovertarget="std-menu"、開く中身に popover="auto" を書いただけで、開閉のJavaScriptを一行も書いていません。外側クリックとESCで閉じるのも、トップレイヤーに出て背後の要素に隠れないのも、ブラウザが面倒を見ています。自作版はこのあと見るように、同じ挙動をひととおり自分で書く必要があります。
比較表
標準機能版と自作版で、実装したときに実際に差が出る項目を並べます。とくに「軽い閉じ」と「重なり順」の列が、自作だと手間や不具合になりやすいところです。
| 項目 | Popover API(popover 属性) | 自作ポップオーバー(div + JS) |
|---|---|---|
| 開く/閉じるの基本 | popovertarget を持つボタンで開閉できる。JavaScriptでは showPopover() / hidePopover() / togglePopover() |
クラスの付け替えや display の切り替えを自分で書く |
| 軽い閉じ(外側クリック・ESC) | popover="auto" なら標準で有効。外側クリックとESCで自動的に閉じる |
document のクリックと keydown を監視して自分で閉じる処理を書く |
| 重なり順(z-index) | トップレイヤーに表示され、親の overflow: hidden やスタッキングコンテキストの影響を受けない。z-index指定が不要 |
祖先の overflow や transform で切れる・隠れることがあり、z-indexの調整が要る |
| 位置合わせ | CSS Anchor Positioning(anchor-name / position-anchor)で開き元にひも付けられる。ただし対応状況に幅があるためフォールバックを併記する(後述) |
position: absolute と親の relative、あるいはJSで座標計算する。はみ出し対策も自前 |
| 他のポップオーバーとの排他 | popover="auto" 同士は、別のを開くと前のが自動で閉じる |
開いているものを閉じてから開く処理を自分で管理する |
| アクセシビリティ | トリガーと中身の関連付け・フォーカスの扱いが組み込みで、キーボード操作も標準の挙動になる | aria-haspopup / aria-expanded の付与やフォーカス移動を自分で担保する |
| スタイリングの自由度 | 中身は自由。要素に既定の border・padding・margin があるためリセットが要る |
素の div なので完全に自由 |
| 開閉アニメーション | 可能だが display の遷移に transition-behavior: allow-discrete などの工夫が要る |
クラス付け替えで作りやすく、細かい演出がしやすい |
| ブラウザ対応 | popover 属性・軽い閉じ・トップレイヤー表示は Chrome・Edge・Firefox・Safari の現行版で対応済み |
普通のDOM操作なので対応の心配は基本的にない |
| 実装量の目安 | マークアップに属性を2つ書く程度。JSは項目選択後に閉じたいときだけ | 開閉・外側クリック・ESC・排他・位置合わせまで含めると数十行になりがち |
popover 属性がJavaScriptなしで持っている挙動
比較表の中身を、実際のコードとして補足します。基本はボタンに popovertarget、開く中身に popover を書くだけです。
<button popovertarget="menu">メニューを開く</button> <div id="menu" popover="auto"> <button>詳細を表示</button> <button>削除する</button> </div>
popover が付いた要素は既定で display: none になり、トリガーを押すと表示されます。表示中はトップレイヤー(他の要素より前面の専用レイヤー)に置かれるため、親要素の overflow: hidden で切れたり、別の要素のスタッキングコンテキストに埋もれたりしません。自作ポップオーバーで悩みがちな重なり順の問題が、ここで構造的に消えます。
popover="auto" を指定すると、外側クリックとESCキーでの「軽い閉じ(light dismiss)」が既定で有効になります。また auto のポップオーバー同士は排他的で、別のを開くと前のは自動で閉じます。逆に、開いたままにしたい・自分の閉じるボタンだけで閉じたい場合は popover="manual" を使います。
JavaScriptから制御したいときは、要素の showPopover() / hidePopover() / togglePopover() を呼びます。上のデモでも、メニュー項目を押したあとに閉じる部分だけ hidePopover() を使っています(開閉そのものは属性任せ)。
const menu = document.getElementById('menu');
// 項目を選んだら閉じる。開く操作は popovertarget 任せでよい
menu.querySelectorAll('button').forEach((btn) => {
btn.addEventListener('click', () => menu.hidePopover());
});
注意点として、popover の中身にはブラウザ既定の border・padding・margin と、中央寄せの位置指定が付いています。デザインを当てるときはこのリセットから始めます。
位置合わせ(CSS Anchor Positioning)の扱い
Popover APIは「開く・閉じる・重なり順」は肩代わりしてくれますが、「開き元ボタンの真下に出す」といった位置合わせは別の話です。既定ではトップレイヤーの中央あたりに出るため、メニューやツールチップとして使うには位置を指定する必要があります。
これをCSSだけで書けるのが CSS Anchor Positioning です。開き元に anchor-name を付け、ポップオーバー側で position-anchor でひも付けると、そのアンカーを基準に配置できます。
/* 開き元ボタン */
#menu-btn { anchor-name: --menu-btn; }
/* ポップオーバー側:ボタンの真下に置く */
#menu {
position-anchor: --menu-btn;
position-area: bottom center;
margin-top: 6px;
}
ただしこの CSS Anchor Positioning は、執筆時点でブラウザによって対応状況に差があります(Chromium系では利用できますが、すべてのブラウザで安定して使える段階には至っていません)。実装時は最新の対応状況を確認したうえで、未対応ブラウザ向けのフォールバックを用意するのが安全です。上のデモでも @supports で分岐し、未対応時は画面中央への固定表示に切り替えています。
/* position-anchor 非対応ブラウザでは中央固定にフォールバック */
@supports not (position-anchor: --x) {
#menu {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
位置合わせをCSSに寄せられるかどうかがはっきりしない現状では、位置がシビアなUI(アンカーにぴったり吸着させたいツールチップなど)では、JSで座標を計算する自作アプローチのほうが読みやすいこともあります。「popoverの軽い閉じとトップレイヤーは使いたい、でも位置はJSで決めたい」という併用も選択肢に入れてよいところです。
dialog要素との違い
「トップレイヤーに出る標準要素」という点で、Popover APIは <dialog> と似ています。ただし性格ははっきり分かれます。
モーダルかどうか
dialog.showModal() はモーダルです。背後のコンテンツを不活性化(inert)し、フォーカスをダイアログ内に閉じ込め、::backdrop で暗幕を出します。一方 popover="auto" は非モーダルで、背後は操作可能なまま、外側クリックでふわっと閉じます。「操作をいったん止めて確認・入力させる」ならdialog、「補助的なメニューやヒントを添える」ならpopover、という住み分けです。
位置の考え方
dialogは画面中央に出す前提のUIで、アンカー位置制御は基本的に想定していません。popoverは開き元にひも付けて配置する使い方が中心で、そのために CSS Anchor Positioning と組み合わせます。ボタンの近くに小さく出したいならpopover、画面を占有して見せたいならdialogが素直です。
モーダルの比較(showModal() と自作モーダルの実装量・フォーカストラップ・::backdrop)は別記事で詳しく扱っています。あわせてdialog要素と自作モーダルはどっちを使うべきかを参照してください。確認・入力・詳細表示のようなモーダルはそちら、行メニューやヒントのような非モーダルはこの記事の内容が近い、という切り分けになります。
業務アプリでの選び方
ここまでを踏まえた判断軸です。
行メニュー・ヒント・簡単なドロップダウンは popover
テーブル行の「…」アクションメニュー、フォームの補足ヒント、ちょっとしたドロップダウン。業務アプリのポップオーバーはほとんどこれで、軽い閉じとトップレイヤー表示が効くだけで実装がかなり軽くなります。とくにテーブルやカードの中に置くメニューは、自作だと親の overflow で切れがちなので、popoverの恩恵が大きい場面です。
凝った演出やアンカー吸着が要るときは自作を検討
開閉アニメーションを細かく作りたい、既存の状態管理・デザイン基盤に完全に載せたい、あるいは位置合わせをJSで厳密に制御したい場合は自作に寄せます。その場合も、軽い閉じとESC・排他制御は自分で書く前提で見積もってください。ここがpopoverが肩代わりしていた部分です。
なお、popoverでもアニメーションは可能です。表示・非表示の display 切り替えに transition-behavior: allow-discrete を組み合わせて遷移させます。ただこの書き方は自作のクラス付け替えより理解のハードルが高いのは事実です。「アニメーションのためだけに自作へ倒す」なら、まずpopoverでの実現可否を確認してからにするのがおすすめです。
まとめ
行メニューやヒントのような非モーダルのポップオーバーは、まず popover 属性から始めるのが妥当です。開閉・軽い閉じ・トップレイヤー表示という、自作だと手間になりやすい部分を標準で持っていて、現行の主要ブラウザすべてで動きます。z-indexや親の overflow と戦わずに済むのは、地味ですが効きます。
気をつけるのは位置合わせで、CSS Anchor Positioning はまだ対応に幅があるため、フォールバックを用意するか、位置だけJSで決める併用を選びます。自作へ全面的に倒すのは、凝った演出や既存基盤への統合という明確な理由があるときに限る、というのがこの記事の結論です。標準のPopover APIを使った実装は、下の動くサンプルで確認できます。