ドロップダウンメニューや補足パネルを作るとき、表示・非表示だけならCSSでも実装できます。しかし、外側をクリックしたら閉じる、Escキーで閉じる、ほかの一時表示と重ならないようにする、といった挙動まで自作するとコードが増えます。
Popover APIを使うと、HTMLのpopover属性とpopovertarget属性を中心に、一時的なUIをブラウザ標準の仕組みで開閉できます。この記事ではプロフィールメニューを作ります。
Popover APIでできること
popovertargetを付けたボタンから対象を開閉できる- 一時表示の外側を操作したときに閉じる挙動を利用できる
- Escキーで閉じる操作を標準の流れに乗せられる
- JavaScriptから
showPopover()、hidePopover()、togglePopover()を呼べる :popover-openで開いている状態だけを装飾できる
完成コード
HTML
<main class="demo-card">
<img class="nines-logo" src="https://kjnine.com/wp-content/themes/kjnines_theme/images/svg2/nines_logo.svg" alt="NINES">
<p class="eyebrow">NATIVE POPOVER</p>
<h1>プロフィールメニュー</h1>
<button class="menu-button" type="button" popovertarget="profileMenu">
メニューを開く
</button>
<nav id="profileMenu" class="profile-menu" popover aria-label="プロフィールメニュー">
<a href="#profile">プロフィール</a>
<a href="#settings">設定</a>
<button type="button" data-logout>ログアウト</button>
</nav>
</main>CSS
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 24px;
background: #eef2f7;
color: #111;
font-family: system-ui, sans-serif;
}
.demo-card {
position: relative;
width: min(100%, 520px);
padding: 40px;
border: 1px solid #dce2ec;
background: #fff;
}
.nines-logo {
position: absolute;
top: 18px;
right: 18px;
width: 58px;
opacity: 0.16;
}
.eyebrow {
color: #0057ff;
font-size: 0.78rem;
font-weight: 800;
letter-spacing: 0.14em;
}
.menu-button {
min-height: 44px;
padding: 10px 16px;
border: 0;
background: #0057ff;
color: #fff;
cursor: pointer;
font: inherit;
font-weight: 800;
}
.profile-menu {
width: min(260px, calc(100vw - 32px));
margin: auto;
padding: 8px;
border: 1px solid #cfd6e3;
background: #fff;
box-shadow: 0 18px 50px rgb(15 30 60 / 22%);
}
.profile-menu a,
.profile-menu button {
display: block;
width: 100%;
padding: 12px;
border: 0;
background: transparent;
color: #111;
text-align: left;
text-decoration: none;
cursor: pointer;
font: inherit;
}
.profile-menu a:hover,
.profile-menu button:hover,
.profile-menu a:focus-visible,
.profile-menu button:focus-visible {
background: #edf3ff;
outline: 2px solid #0057ff;
outline-offset: -2px;
}
@media (prefers-reduced-motion: no-preference) {
.profile-menu:popover-open {
animation: popover-in 160ms ease-out;
}
@keyframes popover-in {
from {
opacity: 0;
transform: translateY(-8px) scale(0.98);
}
}
}JavaScript
const menu = document.querySelector('#profileMenu');
const logoutButton = document.querySelector('[data-logout]');
menu.addEventListener('toggle', (event) => {
console.log(`Popover is now ${event.newState}`);
});
logoutButton.addEventListener('click', () => {
menu.hidePopover();
alert('ログアウト処理へ進みます。');
});HTMLだけで開閉する仕組み
ボタンのpopovertarget="profileMenu"と、表示する要素のid="profileMenu" popoverを一致させます。これだけで開閉できます。単純なメニューなら、開閉のためだけのJavaScriptは必要ありません。
popoverのautoとmanual
| 指定 | 特徴 | 向いている用途 |
|---|---|---|
popover または popover="auto" | 外側操作やEscで閉じる一時表示 | メニュー、補足、選択肢 |
popover="manual" | JavaScriptなどで明示的に閉じる | 複数同時表示、通知、独自制御 |
通常のドロップダウンメニューはautoから始めると自然です。通知を複数残したい場合など、軽い解除で閉じてほしくないUIではmanualを検討します。
JavaScriptが必要になる場面
- データ取得後に開きたい
- 特定の処理完了後に閉じたい
- 開閉状態をログや分析へ送信したい
- フォーム入力の状態に応じて開閉を止めたい
開閉状態はtoggleイベントで確認できます。サンプルではログアウトボタンを押したときにhidePopover()を呼び、処理へ進む前にメニューを閉じています。
dialogとの使い分け
| UI | ユーザー操作 | 例 |
|---|---|---|
| Popover | 一時的な補助。外側操作で閉じてもよい | メニュー、ツールチップ、補足 |
| dialog | 回答や確認を求める。操作を中断させる | 削除確認、同意、入力フォーム |
アクセシビリティの注意点
- 開閉ボタンの文言だけで目的が分かるようにする
- ナビゲーションなら
navと適切なラベルを使う - リンクと実行ボタンを役割に合わせて使い分ける
- ホバーだけを開閉条件にせず、キーボードとタップでも操作できるようにする
- 開いた後のフォーカス順を実機で確認する
ボタンの近くへ配置する
Popover APIは開閉を担当します。表示位置をトリガーボタンへ追従させたい場合は、CSS Anchor Positioningを組み合わせると、絶対位置の座標計算を減らせます。未対応環境も考慮し、中央表示など読めるフォールバックを残します。
CodePen埋め込み用エリア
HTML、CSS、JavaScriptを各パネルへ貼り付けて試せます。最初はJavaScriptなしでも開閉できることを確認し、その後でtoggleイベントを追加してください。
あわせて読みたい
参考資料
まとめ
Popover APIを使うと、ドロップダウンメニューの開閉をHTML中心に実装できます。まずpopoverとpopovertargetで基本を作り、必要な場面だけJavaScriptを足すのが分かりやすい構成です。モーダルはdialog、位置合わせはCSS Anchor Positioningと役割を分けましょう。