本文へスキップ

ポップオーバー

ボタン横に矢印付きの小パネル(メニュー)がフェードイン。外側クリックで閉じる。コンテキストメニューの基本形。

プレビュー

クリック

操作クリック/タップで動きます。

使いどころ

「もっと見る」「…」といった三点リーダーボタンや、行内アクション(編集・コピー・削除)をまとめる用途に向く。単一のアンカーに紐づく少数の選択肢(目安2〜6個)を、画面を暗くせず軽く出せるのが利点で、選んだらすぐ閉じる操作に向く。逆に、選択肢が多くスクロールが必要になる、あるいは選んだ後も内容を確認しながら別の操作を続けたい場合はドロワーやモーダルの方が向く。また、テーブルの行ごとに同じポップオーバーを大量に置くレイアウトでは、アンカーとの位置合わせ(画面端でのはみ出し)を個別に検証しないと、右端の行だけメニューが画面外にはみ出す事故が起きやすい。破壊的な操作(削除)を含める場合は、ポップオーバーの中でさらに確認を挟むか、色を分けて誤操作を防ぐ。

仕組み

アンカー直下に position: relative の .anchor-wrap を置き、その中で popover を position: absolute; top: calc(100% + 10px) として下に固定表示する。矢印は ::before と ::after の border-triangle テクニックで、下向きの三角形を2枚重ね、下側(::before)を枠線色、1px内側にずらした上側(::after)を背景色にすることで「縁取り付きの吹き出し」に見せている。開閉は opacity と transform: translateY() のトランジションのみで、backdrop は使わず「外側クリックで閉じる」を document 全体の click リスナーで実現している。キーボード操作は ArrowDown/ArrowUp で role="menuitem" の要素間を巡回させるroving focusで、Home/Endで先頭・末尾へ飛べる。

実装の注意

モーダルやドロワーと同じ理由で、.popover は opacity と pointer-events だけで隠しており display:none にしていないため、閉じている間もメニュー項目がTabキーで到達できてしまう。ポップオーバーは軽量な部品として何個も並べがちなので、この漏れが起きると1ページに閉じたメニューの数だけ「見えないのに押せるボタン」が積み上がる影響が大きい。もう一つの落とし穴は位置計算で、top: calc(100% + 10px) と left: 50%; transform: translateX(-50%) は中央揃えの単純な固定値なので、画面右端や下端に近いアンカーではポップオーバーがビューポート外へはみ出す。実運用では getBoundingClientRect() で余白を測り、はみ出す側だけ left/right や top/bottom を反転させる分岐が要る。外側クリック判定は e.target !== anchor だけを見ているため、アンカーの中に子要素(アイコンなど)がある構成に変えるときは contains() 判定に直す必要がある。

アクセシビリティ

aria-haspopup="true" と aria-expanded で開閉状態をアンカー側に伝え、popover 内は role="menu"、各項目は role="menuitem" という WAI-ARIA の Menu Button パターンに沿っている。矢印キーでの項目間移動、Home/Endでの端移動も実装済み。ただし本来の Menu パターンでは、開いた状態でTabを押すとメニューごと閉じてフォーカスは次のページ要素へ抜けるのが一般的な挙動だが、この実装はモーダルと同様に最初と最後の項目でTabを折り返す「トラップ」にしてあり、通常のメニューの慣習とは異なる。動作として壊れているわけではないが、他のメニューに慣れたユーザーの期待とはややズレるため、意図的な選択として認識しておくとよい。Escapeで閉じたときはアンカーへフォーカスを戻し、操作の起点を見失わないようにしている。

調整して使う

出現位置(アンカーの下・上・左右)と矢印の向きはセットで変える。画面端では前述の反転ロジックを足すのが前提。項目数が増える場合は min-width を保ったまま max-height と overflow-y: auto を足してスクロール式にできるが、その場合はArrowDown/Upでのフォーカス移動時に scrollIntoView も呼ぶ必要がある。危険な操作(削除など)は pop-item--danger のように色を分けるだけでなく、区切り線(hr)で安全な操作と離す配置がすでに入っているので、新しい危険操作を足すときも同じ位置関係を踏襲するとよい。トランジションの持続時間(0.18s)は開く頻度が高いUIほど短くして体感の軽さを優先するのが基本方針。

コード(コピーして使えます)

<div class="stage" id="stage">
  <div class="anchor-wrap">
    <button class="anchor-btn" id="popoverAnchor" aria-haspopup="true" aria-expanded="false">
      オプション
      <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
        <polyline points="2,4 6,8 10,4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
      </svg>
    </button>

    <div class="popover" id="popover" role="menu" aria-hidden="true">
      <div class="popover-arrow" aria-hidden="true"></div>
      <button class="pop-item" role="menuitem">
        <svg width="13" height="13" viewBox="0 0 13 13" fill="none">
          <path d="M2 10.5h9M8.5 2.5l2 2-6 6-2.5.5.5-2.5 6-6z" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
        編集する
      </button>
      <button class="pop-item" role="menuitem">
        <svg width="13" height="13" viewBox="0 0 13 13" fill="none">
          <rect x="2" y="4" width="9" height="7" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
          <path d="M4.5 4V3a2 2 0 014 0v1" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
        </svg>
        コピーする
      </button>
      <hr class="pop-divider"/>
      <button class="pop-item pop-item--danger" role="menuitem">
        <svg width="13" height="13" viewBox="0 0 13 13" fill="none">
          <path d="M2 3.5h9M5 3.5V2.5a.5.5 0 01.5-.5h2a.5.5 0 01.5.5v1M4.5 3.5l.5 7M8.5 3.5l-.5 7" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
        削除する
      </button>
    </div>
  </div>
</div>

タグ

同じカテゴリ「通知・オーバーレイ」のほかのパーツ

関連パーツ