本文へスキップ

ポップオーバー

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

プレビュー

クリック

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

仕様書

向く場面
  • 「…」ボタンにまとめる行内アクション
  • 2〜6 個の選択肢を画面を暗くせず出す
  • 選んだらすぐ閉じる操作
避ける場面
  • スクロールが要るほど多い選択肢
  • 選んだ後も内容を見ながら続ける作業
  • 画面端で位置補正せずに置く
構成要素
  • アンカーボタン
  • 吹き出しの矢印
  • メニュー
  • メニュー項目
  • 区切り線
  • 危険操作
持っている状態
  • 通常
  • ホバー
  • フォーカス
  • 押下中
キーボード

Enter/Space で開いて先頭項目へ、↑↓で巡回、Home/End で端、Tab はメニューを閉じて次の要素へ抜ける、Esc で閉じてアンカーへ戻る

支援技術

APG Menu Button: aria-haspopup+aria-expanded、role=menu/menuitem、閉じている間は inert+visibility:hidden。矢印は aria-hidden。外側クリックで閉じる

動き

出現 180ms(opacity+6px 降下)。reduced-motion で停止

デザインの要点

  • 開いたら先頭項目へフォーカスを移し、↑↓で巡回・Tab で閉じて抜ける。メニューの中で Tab を閉じ込めない
  • 吹き出しの矢印は ::before(枠線色)と ::after(背景色・1px 下)の三角 2 枚で縁取りを作る。画像もSVGも要らない
  • 出現は 6px 降下+フェード 180ms。アンカーから「垂れ下がる」向きに動かすと、どのボタンのメニューかが分かる
  • 危険な操作は区切り線の下に離し、色を変える。隣に並べると「コピー」の指が「削除」に届く

使いどころ

「もっと見る」「…」といった三点リーダーボタンや、行内アクション(編集・コピー・削除)をまとめる用途に向く。単一のアンカーに紐づく少数の選択肢(目安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 だけで隠すと、閉じている間もメニュー項目がTabキーで到達できてしまう。ポップオーバーは軽量な部品として何個も並べがちなので、この漏れが起きると1ページに閉じたメニューの数だけ「見えないのに押せるボタン」が積み上がる影響が大きい。この見本では閉じたときに inert を付け、CSS でも visibility:hidden(フェードが終わってから)を当てて二重に防いでいる。もう一つの落とし穴は位置計算で、top: calc(100% + 10px) と left: 50%; transform: translateX(-50%) は中央揃えの単純な固定値なので、画面右端や下端に近いアンカーではポップオーバーがビューポート外へはみ出す。実運用では getBoundingClientRect() で余白を測り、はみ出す側だけ left/right や top/bottom を反転させる分岐が要る。外側クリック判定は anchor.contains(e.target) で見る。e.target !== anchor だけだと、アンカーの中の SVG アイコンをクリックしたときに「外側」と誤判定して開いた瞬間に閉じる。

アクセシビリティ

aria-haspopup="true" と aria-expanded で開閉状態をアンカー側に伝え、popover 内は role="menu"、各項目は role="menuitem" という WAI-ARIA の Menu Button パターンに沿っている。矢印キーでの項目間移動、Home/Endでの端移動も実装済み。Tab の扱いも Menu パターンどおりで、開いた状態でTabを押すとメニューを閉じてフォーカスは次のページ要素へ抜ける(モーダルのように中で折り返さない)。閉じている間は inert と visibility:hidden で Tab 順と読み上げの両方から外し、aria-hidden も同期させる。Escapeで閉じたときはアンカーへフォーカスを戻し、操作の起点を見失わないようにしている。フォーカスは :focus-visible で項目とアンカーに 2px の輪郭を描き、ホバーと同じ背景色も合わせて付ける。

調整して使う

出現位置(アンカーの下・上・左右)と矢印の向きはセットで変える。画面端では前述の反転ロジックを足すのが前提。項目数が増える場合は 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" inert>
      <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>

タグ

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

関連パーツ