ポップオーバーとは

ボタンをクリックすると、その近くに見出し・説明文・ボタンなどを含む小さなパネルが開く部品。

概要

ボタンやアイコンをクリックすると、その近くに小さなパネルが浮かび上がって表示される部品です。 ツールチップより多くの内容(見出しや説明文、ボタンなど)を含められるのが特徴で、外側をクリックするか Esc キーを押すと閉じます。 トリガーとパネルは aria-expanded と aria-controls で関連付けます。

別名

表記ゆれ:ポップオーバー、フローティングパネル、popover

Web 標準の popover 属性(HTML の Popover API)の名前としても使われており、実装方法を問わず一般名詞として定着しています。

AI への伝え方

「ポップオーバーを実装して」で通じます。ホバーで開くツールチップと違い、クリックで開閉する前提であること、 外側クリックと Esc キーで閉じることを伝えておくと仕様がぶれません。

似たパーツとの違い

「ツールチップ」はホバーで開き、1〜2 行の短い説明を表示するだけなのに対し、ポップオーバーはクリックで開き、ボタンなどの操作を含められる点が異なります。 「ドロップダウンメニュー」はリンクの一覧を開く専用の部品で、ポップオーバーはより自由な内容を入れられる汎用のパネルという違いがあります。

デモ

コード

HTML
<div class="popover-demo">
  <div class="popover-demo__item">
    <button
      class="popover-demo__trigger"
      type="button"
      aria-expanded="false"
      aria-controls="popover-demo-panel"
      data-popover-trigger
    >
      詳細を見る
    </button>
    <div class="popover-demo__panel" id="popover-demo-panel" role="dialog" hidden>
      <h3 class="popover-demo__title">プランの詳細</h3>
      <p class="popover-demo__text">月額 1,000 円で、すべての機能を利用できます。いつでも解約できます。</p>
      <div class="popover-demo__actions">
        <button type="button" class="popover-demo__cancel" data-popover-close>閉じる</button>
        <button type="button" class="popover-demo__confirm" data-popover-close>申し込む</button>
      </div>
    </div>
  </div>
</div>
CSS
.popover-demo {
  font-family: sans-serif;
}

.popover-demo__item {
  position: relative;
  display: inline-block;
}

.popover-demo__trigger {
  padding: 10px 16px;
  border: 1px solid #ddd;
  border-radius: 8px;
  background: #fff;
  font-size: 14px;
  cursor: pointer;
}

.popover-demo__trigger:hover {
  background: #f2f2f2;
}

.popover-demo__panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 240px;
  padding: 16px;
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.15);
  z-index: 10;
}

/* hidden 属性の既定スタイル(display: none)は、上の display: flex より
   詳細度が同じため後勝ちで上書きされてしまう。hidden のときは明示的に隠す */
.popover-demo__panel[hidden] {
  display: none;
}

.popover-demo__title {
  margin: 0;
  font-size: 15px;
}

.popover-demo__text {
  margin: 0;
  font-size: 13px;
  color: #333333;
  line-height: 1.6;
}

.popover-demo__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.popover-demo__cancel,
.popover-demo__confirm {
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
}

.popover-demo__cancel {
  border: 1px solid #999999;
  background: #fff;
}

.popover-demo__confirm {
  border: 0;
  background: #0017c1;
  color: #fff;
}
JS
const trigger = document.querySelector("[data-popover-trigger]");
const panel = document.getElementById("popover-demo-panel");

function openPopover() {
  panel.hidden = false;
  trigger.setAttribute("aria-expanded", "true");
}

function closePopover() {
  panel.hidden = true;
  trigger.setAttribute("aria-expanded", "false");
}

trigger.addEventListener("click", () => {
  if (panel.hidden) {
    openPopover();
  } else {
    closePopover();
  }
});

document.querySelectorAll("[data-popover-close]").forEach((button) => {
  button.addEventListener("click", closePopover);
});

document.addEventListener("click", (event) => {
  if (!panel.hidden && !panel.contains(event.target) && event.target !== trigger) {
    closePopover();
  }
});

document.addEventListener("keydown", (event) => {
  if (event.key === "Escape") closePopover();
});