トリガー要素(ボタン・アイコンなど)をクリックすると展開するフローティングパネルUIコンポーネント。TooltipとModal Dialogの間を埋める存在で、インタラクティブなコンテンツ(リンク・フォーム・設定・フィルター)を小さなパネルで提供することが目的。モーダルと異なり背後のコンテンツを操作できる状態で共存する。
この記事を読むと、Tooltip・Modal・Dropdown Menuとの使い分け・フォーカス管理(開いたらパネル内の最初の要素にフォーカス)・Escape での閉じ方・ARIA dialog パターンとの使い分けが自分でできるようになります。
1. UI例(Preview / Live)
実装で見る(GunjoUI)
この部品を、デザインシステム GUNJO の実装で確かめられます。
2. 定義(Definition)
トリガー要素をクリックすると展開するフローティングパネルUIコンポーネント。インタラクティブなコンテンツ(リンク・フォーム・設定)を含められる点でTooltipと異なり、背後のコンテンツを操作できる状態で共存できる点でModal Dialogと異なる。軽量な設定パネル・フィルター・カラーピッカー・ヘルプパネルに適する。
TooltipとPopoverの違い:Tooltipはホバー/フォーカスで表示される受動的な補足(テキストのみ・インタラクション不可)。Popoverはクリックで開く能動的なパネル(リンク・フォームを含む・インタラクション可)。
ModalとPopoverの違い:Modalは背後を操作できなくなるフォーカストラップ付きのオーバーレイ。Popoverは背後を操作できる状態で表示されるパネル。フォーカストラップの有無が主な違い。複雑な操作・重要な確認にはModalを使う。
3. 使い分け(When to use / When NOT to use)
3.1 When to use
- 軽量な設定パネル(通知設定・表示設定など3〜5項目程度)
- フィルター/ソートパネル(チェックボックス複数・スライダー)
- カラーピッカー・日付ピッカーなどウィジェット型UI
- ヘルプ / 情報パネル(リンク付きの補足説明)
- インラインでの簡易編集(テキスト・タグの編集)
3.2 When NOT to use
- 複雑なフォーム(10項目以上・確認が必要な操作)→ Modal Dialogを使う
- 重要な確認・警告(削除確認など)→ Alert Dialogを使う
- テキストのみの短い補足 → Tooltipを使う
- コンテンツが多い(スクロールが必要なほど)→ Sheet / Drawerを使う
3.3 代替UI(Alternatives)
- テキストの補足 →
Tooltip - ユーザー情報のプレビュー →
Hover Card - 複雑な操作・確認 →
Modal Dialog/Alert Dialog - 多くのコンテンツ →
Sheet / Drawer
4. 設計判断の核(Decision Principles)
Popoverの核は「Modal未満・Tooltip超え」の適切なサイズ感——背後を操作できる軽量さを保ちながら、インタラクティブなコンテンツを提供する。複雑になりすぎたらModalに昇格させる判断が重要。
判断の優先順位:① 複雑さの判断(Popover vs Modal)→ ② フォーカス管理 → ③ Escape で閉じる → ④ 外クリックで閉じる
- 3〜5項目の設定・フィルターならPopover、それ以上ならModal:Popoverの中身が複雑になるほど、「この操作は重要なのにモーダルでなく軽く扱われている」というユーザーの不安が増す。操作の重要度・項目数でModalへの昇格を判断する
- 開いたら最初のインタラクティブ要素にフォーカスを移す:Popoverが開いたら
autoFocusまたはuseEffectで最初の入力要素・ボタンにフォーカスする。フォーカスが移らないとキーボードユーザーがPopover内を操作できない - Escapeで閉じてトリガーにフォーカスを戻す:EscapeキーでPopoverを閉じた後、開いたトリガーボタンにフォーカスを戻す。これがないと次のTabキー操作で意図しない場所にジャンプする
- 外クリックで閉じる:Popover外の要素をクリックした時にPopoverを閉じる。ただしトリガーボタン自体のクリックは開閉のトグルとして機能させる
5. 状態設計(States)
5.1 必須状態(Required)
- Closed(閉じた状態):トリガーボタンのみ表示
- Open(展開中):パネルが表示され、最初のインタラクティブ要素にフォーカス
5.2 条件付き状態(Conditional)
- Loading:パネルのコンテンツがサーバーから取得中
- Error:コンテンツ取得失敗
5.3 State Gallery
| 状態 | 必須 | 何を伝えるか |
|---|---|---|
| Closed | ✅ | 展開可能(aria-haspopup で伝える) |
| Open | ✅ | パネルのコンテンツ |
6. バリエーション設計(Variants)
コンテンツの種類によって使い分ける。
| バリアント | コンテンツ | 使用例 |
|---|---|---|
| 設定パネル | チェックボックス + ボタン | 通知設定・表示オプション |
| フィルターパネル | チェックボックス複数 + リセット | 一覧画面のフィルター |
| ウィジェット | カラーピッカー・日付ピッカー | カラー選択・日付入力 |
| 情報パネル | テキスト + リンク | ヘルプ・ツールの説明 |
禁止パターン:Popoverにフォーカストラップを実装する → フォーカストラップを付けるとModalと同等の重さになる。フォーカストラップが必要なほど重要な操作なら最初からModalを使う。
7. パターン集(Good / Bad / How to fix)
7.0 よく崩れる設計パターン(3つ)
- Popoverが開いてもフォーカスが移らない:キーボードユーザーがPopoverの存在に気づかず、Tabキーで次の要素に進んでしまう
- Escapeで閉じた後フォーカスが行方不明:Popoverを閉じた後フォーカスが失われ、次のTabキーでページの先頭から操作し直しになる
- Popoverの複雑化:Popoverの中にネストしたPopover・複雑なフォーム・長いコンテンツを詰め込み、Modal相当の重さになる
7.1 Bad(典型3つ)
<div onClick={open}>設定</div>でPopoverを開くが、キーボードで操作できない(<button>ではない)- Popoverが開いた後、フォーカスがトリガーボタンに残ったままで、Tabキーを押さないとPopover内の操作ができない
- Popoverの中に10項目以上のフォームと「キャンセル」「確認」ダイアログを含め、実質的にModalと同等の操作になっている
7.2 Good(対になる3つ)
<button aria-haspopup="dialog" aria-expanded={isOpen}>設定</button>でトリガーを実装し、キーボードでも開閉できる- Popoverが開いたら
useEffectでpanelRef.current?.querySelector('button, input, a')?.focus()を呼び、最初のインタラクティブ要素にフォーカスを移す - 項目が増えてきたらPopoverをModalに昇格させ、フォーカストラップ・スクリーンリーダー向けの
aria-modal="true"を追加する
7.3 How to fix(手順)
- トリガーに
<button aria-haspopup="dialog" aria-expanded={isOpen}>を使う - Popoverが開いた時の
useEffectで最初のbutton, input, a, [tabindex]にフォーカスを移す onKeyDownにEscapeを実装し、Popoverを閉じた後triggerRef.current?.focus()でトリガーにフォーカスを戻すmousedownイベントで Popover外クリックを検知し、閉じる処理を実装する
8. ルール(Must / Better)
Must(守らないと壊れる)
Better(品質が跳ねる)
9. アクセシビリティ要件(必須)
Keyboard
Enter/Spaceでトリガーボタンを操作してPopoverを開くTab/Shift+TabでPopover内のフォーカス可能要素を移動EscapeでPopoverを閉じてトリガーにフォーカスを戻す
Focus
- Popoverが開いたら最初のインタラクティブ要素にフォーカスを移す
- 閉じた後はトリガーボタンにフォーカスを戻す
- フォーカストラップは不要(Popover外をTabで抜けられる)
Screen Reader
<button
aria-haspopup="dialog"
aria-expanded="true"
aria-controls="popover-settings"
>
詳細設定
</button>
<div
id="popover-settings"
role="dialog"
aria-label="通知設定"
>
<!-- コンテンツ -->
</div>
Touch / Pointer
- タップ領域は最低44px
- Popover内のコントロール(チェックボックス・ボタン)も44pxのタップ領域を確保する
10. 実装メモ(Implementation Notes)
- shadcn/ui の
Popoverは@radix-ui/react-popoverベースで、aria-haspopup・フォーカス管理・Escape処理・外クリックで閉じる・Floating UIの位置計算がすべて実装済み - 位置計算(画面端での自動反転)は Floating UI の
useFloating+autoPlacementまたはflipミドルウェアで実装する。@radix-ui/react-popoverでは内部で自動処理される - アニメーションは
data-state="open"/data-state="closed"属性と CSS Keyframes を組み合わせると Radix UI のPopoverでスムーズに実装できる
11. 関連リンク
- 関連するUIデザイン原則: 段階的開示 (Progressive Disclosure), フィードバック (Feedback)
- 用語集(定義): アクセシビリティ (Accessibility)
- 関連するUIコンポーネント(横): Tooltip(ツールチップ), Hover Card(ホバーカード), Modal / Dialog(モーダル), Dropdown Menu(ドロップダウンメニュー)
12. まとめ
Popoverの設計は「Modal未満・Tooltip超え」のサイズ感の見極めが核心です。迷ったら 4. 設計判断の核 に戻り、「3〜5項目程度か(それ以上はModal)」「開いたら最初の要素にフォーカスが移るか」「Escapeでトリガーにフォーカスが戻るか」の3点を確認してください。shadcn/ui の Popover を使えばこれらの実装は自動的に解決されます。Tooltipとの最大の違いは「クリックで開く・インタラクティブ要素を含める」ことであり、この2点を満たす軽量なパネルが必要な時がPopoverの出番です。