Webパーツ辞典

ポップ確認Popconfirm

削除ボタンを押すと、そのすぐそばに「削除しますか?」の小窓が出て、キャンセルと削除するの2つのボタンで答える部品。画面全体は暗くならない。

表示例

請求書_9月.pdf

アイコン: Font Awesome 7 Free(fa-solid / fa-regular)

別名・英語表記

  • ポップコンファーム
  • インライン確認
  • 確認ポップオーバー
  • 削除の確認ポップアップ
  • Popconfirm

実装例

<div class="x-popconfirm">
  <div class="x-popconfirm__row">
    <span class="x-popconfirm__file"><i class="fa-regular fa-file-lines" aria-hidden="true"></i>請求書_9月.pdf</span>
    <button class="x-popconfirm__b" type="button" aria-haspopup="dialog" aria-expanded="true" aria-controls="x-popconfirm-p">
      <i class="fa-solid fa-trash-can" aria-hidden="true"></i>削除
    </button>
  </div>
  <!-- 見本なので開いた状態で流れの中に置いている。実際は小窓に popover 属性、「削除」に popovertarget="x-popconfirm-p" を付けて開閉し
       (hidden 属性では閉じない)、aria-expanded は false から始めて toggle イベントで書き換える。開いたらフォーカスを「キャンセル」へ移す。
       Esc か hidePopover() で閉じると、ブラウザはフォーカスを開く前の要素へ戻す。「削除」の click で先に this.focus() を呼び、
       戻る先を「削除」にする(Safari と macOS の Firefox は、押したボタンにフォーカスを移さない) -->
  <div class="x-popconfirm__p" id="x-popconfirm-p" role="dialog" aria-labelledby="x-popconfirm-t" aria-describedby="x-popconfirm-d">
    <p class="x-popconfirm__t" id="x-popconfirm-t">
      <i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i>このファイルを削除しますか?
    </p>
    <p class="x-popconfirm__d" id="x-popconfirm-d">共有中の相手も開けなくなります。</p>
    <div class="x-popconfirm__act">
      <button class="x-popconfirm__cancel" type="button">キャンセル</button>
      <button class="x-popconfirm__ok" type="button">削除する</button>
    </div>
  </div>
</div>

AIへの指示文

「ポップ確認(Popconfirm)」を実装してください。

## これは何か
削除ボタンを押すと、そのすぐそばに「削除しますか?」の小窓が出て、キャンセルと削除するの2つのボタンで答える部品。画面全体は暗くならない。

## 使いどころ
- 一覧の行ごとの削除など、取り返しのつかない軽い操作を、その場で1回だけ確かめたいとき
- 確認のたびに画面全体を覆うダイアログを出すと、作業の流れが途切れるとき

## 実装の要件
- button[aria-haspopup=dialog][aria-expanded][aria-controls] + 小窓(問いかけ・補足・キャンセルと実行の2ボタン)
- 小窓に popover 属性、開くボタンに popovertarget を付ける。外側クリックと Esc で閉じる動きと重ね順はブラウザが持つ(Chrome・Edge は 114 から、Safari は 17 から、Firefox は 125 から。iOS の Safari が外側のタップで閉じるのは 18.3 から)
- 開いたらキャンセルへフォーカスを移すので、「削除」の click で先に this.focus() を呼ぶ。Safari と macOS の Firefox はマウスで押したボタンにフォーカスを移さず、そのままだと Esc で閉じたときに「削除」へ戻らない
- 小窓に position: relative を付けない。見本の position: relative は流れの中で矢印を置くためのもので、popover にすると最前面の層で absolute として扱われ、@supports not の枝では文書の先頭を基準に置かれて画面の外に出る。矢印の基準は popover の既定の position: fixed が担う
- 位置は CSS のアンカー配置で決める。popovertarget で開いた小窓は、押したボタンが暗黙のアンカーになる(Chrome・Edge は 133 から、Safari は 26 から、Firefox は 147 から)
- @supports (position-area: block-end) の中で margin: 10px 0 0; inset: auto; position-area: block-end span-inline-start; position-try-fallbacks: flip-block; を指定し、ボタンの下に右端をそろえて出す
- 下にはみ出すときはブラウザが上へ反転させ、隙間の margin も上下を入れ替える
- position-area に対応しないブラウザ向けに、@supports not (position-area: block-end) の中で小窓に margin: auto; を書き、画面の中央に出す(popover の既定の margin: auto に頼ると、margin を 0 にするリセット CSS に消されて画面の端に張りつく)
- ボタンを指す矢印は @supports (container-type: anchored) の中でだけ小窓の ::before で描く(Chrome・Edge は 143 から。Safari と Firefox は未対応なので矢印なしで出す)
- popover の既定の overflow: auto では枠の外の矢印が切れるので、小窓に overflow: visible; container-type: anchored; を付ける。上へ反転したときは @container anchored(fallback: flip-block) で矢印を下辺の下向きに付け替える
- 重い判断にはダイアログを使う。アカウントの削除や支払いのように影響の大きい操作は、画面の中央で理由まで説明する
- 取り消せる操作なら確認を挟まず、実行してからスナックバーで「元に戻す」を出すほうが手間が少ない
- ボタンの文言を「はい/いいえ」「OK」にしない。「削除する/キャンセル」のように動作を書く
- 外側を押したら「キャンセル」と同じ扱いで閉じる。押したら実行、にはしない

## アクセシビリティの要件
- 開くボタンに aria-haspopup="dialog" と aria-expanded を付け、aria-controls で小窓を指す
- aria-expanded は小窓の toggle イベントの newState(open / closed)に合わせて true / false に書き換え、外側クリックや Esc で閉じたときも古い値を残さない
- 小窓は role="dialog" にし、問いかけと補足を aria-labelledby / aria-describedby で結ぶ
- 開いたらフォーカスを危険でないほうのボタン(キャンセル)へ移す。Esc で閉じ、開いたボタンへフォーカスを戻す
- 背後は操作できるままなので aria-modal は付けない

## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。

```html
<div class="x-popconfirm">
  <div class="x-popconfirm__row">
    <span class="x-popconfirm__file"><i class="fa-regular fa-file-lines" aria-hidden="true"></i>請求書_9月.pdf</span>
    <button class="x-popconfirm__b" type="button" aria-haspopup="dialog" aria-expanded="true" aria-controls="x-popconfirm-p">
      <i class="fa-solid fa-trash-can" aria-hidden="true"></i>削除
    </button>
  </div>
  <!-- 見本なので開いた状態で流れの中に置いている。実際は小窓に popover 属性、「削除」に popovertarget="x-popconfirm-p" を付けて開閉し
       (hidden 属性では閉じない)、aria-expanded は false から始めて toggle イベントで書き換える。開いたらフォーカスを「キャンセル」へ移す。
       Esc か hidePopover() で閉じると、ブラウザはフォーカスを開く前の要素へ戻す。「削除」の click で先に this.focus() を呼び、
       戻る先を「削除」にする(Safari と macOS の Firefox は、押したボタンにフォーカスを移さない) -->
  <div class="x-popconfirm__p" id="x-popconfirm-p" role="dialog" aria-labelledby="x-popconfirm-t" aria-describedby="x-popconfirm-d">
    <p class="x-popconfirm__t" id="x-popconfirm-t">
      <i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i>このファイルを削除しますか?
    </p>
    <p class="x-popconfirm__d" id="x-popconfirm-d">共有中の相手も開けなくなります。</p>
    <div class="x-popconfirm__act">
      <button class="x-popconfirm__cancel" type="button">キャンセル</button>
      <button class="x-popconfirm__ok" type="button">削除する</button>
    </div>
  </div>
</div>
```

```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-popconfirm,
.x-popconfirm *:not([class*="fa-"]),
.x-popconfirm *:not([class*="fa-"])::before,
.x-popconfirm *:not([class*="fa-"])::after {
  box-sizing: border-box;
  /* 貼った先の既定の書体に引きずられないようにする。書体が変わると
     文字の幅と高さが変わり、枠のサイズまでずれる */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}

.x-popconfirm {
  position: relative;
  width: 300px;
}
.x-popconfirm__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 6px 6px 12px;
  border: 1px solid #e3e6ea;
  border-radius: 8px;
  background: #fff;
}
.x-popconfirm__file {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: #1b2028;
  font-size: 13px;
}
.x-popconfirm__file .fa-regular {
  color: #5b6472;
}
.x-popconfirm__b {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 32px;
  padding: 0 10px;
  border: 1px solid #cfd4dc;
  border-radius: 7px;
  background: #fff;
  color: #b91c1c;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.x-popconfirm__b[aria-expanded="true"] {
  border-color: #b91c1c;
  background: #fef2f2;
}
/* 実際は popover 属性で重ね、位置は CSS のアンカー配置で押したボタンのそばに決める。見本は流れの中に置いている。
   position: relative は流れの中で矢印を置くための見本用で、popover にしたら外す(最前面の層では absolute になる) */
.x-popconfirm__p {
  position: relative;
  width: 264px;
  margin: 10px 0 0 auto;
  padding: 12px 14px;
  border: 1px solid #cfd4dc;
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 8px 20px rgba(27, 32, 40, 0.14);
}
/* 吹き出しの矢印。押したボタンを指す */
.x-popconfirm__p::before {
  content: "";
  position: absolute;
  top: -7px;
  right: 30px;
  width: 12px;
  height: 12px;
  border-top: 1px solid #cfd4dc;
  border-left: 1px solid #cfd4dc;
  background: #fff;
  transform: rotate(45deg);
}
.x-popconfirm__t {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: 0 0 4px;
  color: #1b2028;
  font-size: 13px;
  font-weight: 700;
}
.x-popconfirm__t .fa-solid {
  margin-top: 2px;
  color: #b45309;
}
.x-popconfirm__d {
  margin: 0 0 10px;
  color: #5b6472;
  font-size: 11.5px;
  line-height: 1.6;
}
.x-popconfirm__act {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
}
.x-popconfirm__cancel,
.x-popconfirm__ok {
  min-height: 30px;
  padding: 0 12px;
  border-radius: 6px;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.x-popconfirm__cancel {
  border: 1px solid #7f8a99;
  background: #fff;
  color: #1b2028;
}
.x-popconfirm__ok {
  border: 1px solid #b91c1c;
  background: #b91c1c;
  color: #fff;
  font-weight: 700;
}
.x-popconfirm__b:focus-visible,
.x-popconfirm__cancel:focus-visible,
.x-popconfirm__ok:focus-visible {
  outline: 2px solid #1d4ed8;
  outline-offset: 2px;
}
```

## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。

使いどころ

  • 一覧の行ごとの削除など、取り返しのつかない軽い操作を、その場で1回だけ確かめたいとき
  • 確認のたびに画面全体を覆うダイアログを出すと、作業の流れが途切れるとき

注意点

  • 重い判断にはダイアログを使う。アカウントの削除や支払いのように影響の大きい操作は、画面の中央で理由まで説明する
  • 取り消せる操作なら確認を挟まず、実行してからスナックバーで「元に戻す」を出すほうが手間が少ない
  • ボタンの文言を「はい/いいえ」「OK」にしない。「削除する/キャンセル」のように動作を書く
  • 外側を押したら「キャンセル」と同じ扱いで閉じる。押したら実行、にはしない

アクセシビリティの要点

  • 開くボタンに aria-haspopup="dialog" と aria-expanded を付け、aria-controls で小窓を指す
  • aria-expanded は小窓の toggle イベントの newState(open / closed)に合わせて true / false に書き換え、外側クリックや Esc で閉じたときも古い値を残さない
  • 小窓は role="dialog" にし、問いかけと補足を aria-labelledby / aria-describedby で結ぶ
  • 開いたらフォーカスを危険でないほうのボタン(キャンセル)へ移す。Esc で閉じ、開いたボタンへフォーカスを戻す
  • 背後は操作できるままなので aria-modal は付けない

作るときに使えるツール

見分け方

関連するパーツ