はじめに
ツールチップやドロップダウンメニュー、モーダルといったオーバーレイUIを構築するには、これまで多くのJavaScriptが必要でした。CSSの position: absolute による手動座標計算、ARIA属性を使ったアクセシビリティ対応、ReactポータルやFloating UIなどのフレームワーク依存の解決策——これらすべてに学習コストとメンテナンスの負荷が伴います。
CSS Popover APIは、宣言的なHTML属性のみでオーバーレイを実現するネイティブソリューションです。表示/非表示の切り替え、ライトディスミス、フォーカス管理、アクセシビリティをネイティブに処理し、多くのケースでJavaScriptを不要にします。
1. 基本:popover属性とpopovertarget
Popover APIの核となるのは2つのHTML属性です。popover 属性で要素をポップオーバーとしてマークし、popovertarget 属性でトリガーボタンと紐付けます。
<button popovertarget="my-popover">メニューを開く</button>
<div id="my-popover" popover>
<p>このコンテンツがネイティブポップオーバーで表示されます。</p>
</div>
ボタンをクリックするだけでポップオーバーの表示が切り替わります。popover 属性には2つのモードがあります。
| モード | 動作 | 使用例 |
|---|---|---|
popover="auto" | 外側クリックまたはEscapeで閉じる。同時に1つのみ表示可能 | ツールチップ、ドロップダウンメニュー |
popover="manual" | プログラム的にのみ閉じる。複数同時表示可能 | トースト通知、スライドパネル |
2. Popover Toggle API
プログラムからポップオーバーを制御する場合、HTMLElement に3つのメソッドが用意されています。
const popover = document.getElementById('my-popover');
popover.showPopover(); // 表示
popover.hidePopover(); // 非表示
popover.togglePopover(); // 切り替え
beforetoggle イベントと toggle イベントも利用可能です。beforetoggle は可視性が変更される直前に発火し、oldState と newState プロパティを持ちます。動的なコンテンツの取得やアニメーションの事前準備に活用できます。
popover.addEventListener('beforetoggle', (event) => {
if (event.newState === 'open') {
console.log('ポップオーバーが開こうとしています');
}
});
3. ライトディスミス動作
autoモードのポップオーバーは、以下のタイミングで自動的に閉じます。
- ポップオーバー外をクリックしたとき
- Escapeキーを押したとき
- 別のautoポップオーバーが開かれたとき
この動作は <dialog> と同じclose-watcher基盤によって制御されています。manualモードでは明示的な閉操作が必要なため、ユーザーがアクションを完了するまでオーバーレイを表示し続けたい場合に適しています。
<button popovertarget="toast" popovertargetaction="show">トースト表示</button>
<div id="toast" popover="manual">処理が完了しました</div>
popovertargetaction 属性で "show"、"hide"、デフォルトの "toggle" を指定できます。
4. アンカー配置(Anchor Positioning)
Popover APIはCSS Anchor Positioning APIと統合されており、Popper.jsやFloating UIなしで宣言的な位置決めが可能です。
.popover {
position-anchor: --trigger;
inset-area: block-end;
}
<button id="trigger" popovertarget="tooltip">マウスを重ねてください</button>
<div id="tooltip" popover class="popover" anchor="trigger">ツールチップ内容</div>
anchor 属性でトリガー要素を指定し、inset-area や position-fallback で表示位置を制御します。ビューポートにはみ出す場合は自動的に反対側に反転するため、従来JavaScriptで行っていた位置調整が不要になります。
5. スタイリングとアニメーション
ポップオーバーはトップレイヤーに描画されるため、z-indexのスタックコンテキスト管理から解放されます。::backdrop 疑似要素で背後をスタイリングできます。
[popover]::backdrop {
background: rgba(0, 0, 0, 0.3);
}
[popover]:popover-open 疑似クラスで表示時のスタイルを指定します。ただし、ポップオーバーは display: none と display: block の切り替えで動作するため、CSSトランジションを直接適用できません。@starting-style と overlay プロパティを使った回避策が必要です。
@starting-style {
[popover]:popover-open {
opacity: 0;
translate: 0 -10px;
}
}
[popover]:popover-open {
opacity: 1;
translate: 0 0;
transition: opacity 0.3s, translate 0.3s, overlay 0.3s allow-discrete;
}
6. アクセシビリティ
Popover APIは多くのアクセシビリティ処理を自動化します。コンテキストに応じて適切な暗黙的ロール(group または dialog)を割り当て、開封時にフォーカスを移動し、autoモードではフォーカスをトラップします。Escapeキー処理も自動です。
追加のARIA属性でさらに使いやすくなります。
<button popovertarget="help" aria-describedby="help">ヘルプ</button>
<div id="help" popover role="tooltip">Escapeキーで閉じます。</div>
メニューとして機能するポップオーバーには role="menu" と aria-activedescendant を設定します。スクリーンリーダー(NVDA、VoiceOver、JAWS)でのテストを推奨します。
7. Popover vs. <dialog> vs. カスタムモーダル
| 機能 | Popover API | <dialog> | カスタムモーダル |
|---|---|---|---|
| ライトディスミス | ビルトイン(auto) | 手動実装 | 手動実装 |
| トップレイヤー | 対応 | showModal() で対応 | 要ポータルまたはz-index |
| 開封にJSが必要 | 不要 | 必要 | 必要 |
| フォーカストラップ | autoポップオーバー | モーダルダイアログ | カスタム実装 |
| 最適な用途 | ツールチップ、メニュー、トースト | 確認ダイアログ、フォーム | フレームワーク管理の複雑なオーバーレイ |
確認ダイアログやフォームのようなフォーカストラップが必要なケースでは <dialog> を、軽量な非モーダルオーバーレイにはPopover APIを使用します。
8. ブラウザサポートとPolyfill
Chrome 114+、Edge 114+、Firefox 125+、Safari 17+ でサポートされています。フィーチャーディテクションは以下の方法で行えます。
if (typeof HTMLElement.prototype.showPopover === 'function') {
// Popover APIに対応
}
非対応ブラウザには @oddbird/popover-polyfill が利用可能です。ただし ::backdrop やトップレイヤーの動作を完全に再現できるわけではなく、position: fixed によるフォールバックとなります。
まとめ
CSS Popover APIは、宣言的なUI開発における重要な進歩です。表示/非表示の切り替え、ライトディスミス、フォーカス管理、位置決めをネイティブに処理することで、多くのオーバーレイユースケースからJavaScriptを排除できます。CSS Anchor Positioningと組み合わせれば、ツールチップ、ドロップダウンメニュー、トースト通知、コンテキストメニューを数行のコードで実装できます。
複数ステップのモーダルやフレームワーク管理が必要な複雑なケースでは従来のJavaScriptソリューションが引き続き有効ですが、日常的なオーバーレイパターンの大部分において、Popover APIはよりシンプルでアクセシブル、かつパフォーマンスの高い選択肢です。
