確認アクション
confirm-action は、破壊的なアクション — htmx リクエスト、または
素のフォーム送信 — の前段にモーダルの確認ステップを追加します。
ボタンは普通の <button> のまま、ネットワークリクエストの所有者は
htmx のまま — 小さなビヘイビアがダイアログを開き、ユーザーが承認した
ときに hc:confirmed イベントを発火し直すだけです。htmx を使わない
素のフォームの submit ボタンでは、遮断した送信を form.requestSubmit()
で完了させます。
別名: 確認ダイアログ。
Delete をクリックして承認してみてください — outerHTML スワップで
行が消え、本物の HX-Trigger ヘッダーが削除されたアイテム名を告げる
トーストを発火します。下の「基本の使い方」のサーバなしプレビュー
(結果を表示するだけ)と違い、こちらは実際のエンドポイントを往復します —
サーバ応答契約をそのまま実装したデモで、api/recipes/confirm-action/
配下に置かれています。サーバはステートレスなので、Reset rows は
行を再取得するだけです。
| Item | Actions |
|---|
基本の使い方
Section titled “基本の使い方”htmx リクエストを発行する要素に data-hc-confirm を追加し、htmx の
トリガーを hc:confirmed でゲートします:
<button class="hc-button" data-variant="error" type="button" data-hc-confirm="Delete this item?" data-hx-delete="/items/123" data-hx-trigger="hc:confirmed" data-hx-target="closest tr" data-hx-swap="outerHTML"> Delete</button>試してみてください — Delete をクリックすると共有の確認ダイアログが 開きます。(このデモにはサーバがないため、承認すると htmx リクエストの 代わりに結果を表示します。)
<button class="hc-button" data-variant="error" type="button" data-hc-confirm="Delete this item?" data-hx-delete="/items/123" data-hx-trigger="hc:confirmed" data-hx-target="closest tr" data-hx-swap="outerHTML"> Delete</button>流れは次のとおり:
- ユーザーがボタンをクリックします。
- ビヘイビアがクリックをインターセプトし、共有の確認ダイアログを 表示します。元のクリックは抑止され、htmx はまだ発火しません。
- ユーザーが承認すると、ビヘイビアは元のボタン上でバブリングする
hc:confirmedイベントを発火します。 data-hx-trigger="hc:confirmed"で待ち受けている htmx がリクエストを 送信します。- サーバはターゲット領域の HTML(および任意で
HX-Triggerイベント)を 返します。
ユーザーがキャンセルした場合、イベントは発火されず、何も起きません。
素のフォーム(htmx なし)
Section titled “素のフォーム(htmx なし)”data-hc-confirm は、普通の <form> 内の submit ボタンでもそのまま
使えます — htmx 属性は不要です:
<form method="post" action="/items/123/delete"> <button class="hc-button" data-variant="error" data-hc-confirm="この項目を削除しますか?"> 削除 </button></form>承認すると、ビヘイビアは form.requestSubmit(button) を呼び、遮断した
送信を本来のクリックと同じ形で完了させます: ボタンが submitter になる
(formaction / formmethod が尊重される)ほか、制約バリデーションが
先に実行されます。キャンセルの場合は何も送信されません。
このパスが適用されるのは、ボタンにもフォームにも htmx の verb 属性
(hx-get/post/put/patch/delete および data-hx-* 変種)がない場合
のみです。どちらかにある時点で hc:confirmed の発火だけになり、
リクエストの所有者は htmx のままです — フォームが二重送信されることは
ありません。
JavaScript がない環境でも同じマークアップが優雅に劣化します: フォームは ネイティブに送信され、確認ステップだけがなくなります。
ダイアログのカスタマイズ
Section titled “ダイアログのカスタマイズ”メッセージは data-hc-confirm から取られます。残りの属性は任意です:
| 属性 | デフォルト |
|---|---|
data-hc-confirm | (必須) ダイアログの本文 |
data-hc-confirm-title | Confirm |
data-hc-confirm-label | Confirm |
data-hc-cancel-label | Cancel |
data-hc-confirm-variant | 発火元の data-variant、なければ primary |
<button class="hc-button" data-variant="error" type="button" data-hc-confirm="Permanently delete invoice #123? This cannot be undone." data-hc-confirm-title="Delete invoice" data-hc-confirm-label="Delete" data-hc-cancel-label="Keep" data-hx-delete="/invoices/123" data-hx-trigger="hc:confirmed" data-hx-target="closest tr"> Delete</button>展開後の HTML
Section titled “展開後の HTML”ボタンを .hc-action で包み htmx インジケーターを付けると、計画 §24 の
破壊的アクション体験一式になります:
<span class="hc-action"> <button class="hc-button" data-variant="error" type="button" data-hc-confirm="Delete this item?" data-hx-delete="/items/123" data-hx-trigger="hc:confirmed" data-hx-target="closest tr" data-hx-swap="outerHTML" data-hx-disabled-elt="this" data-hx-indicator="closest .hc-action"> Delete </button> <span class="hc-spinner htmx-indicator" aria-hidden="true"></span></span>@hypermedia-components/core/macros をインポートすれば、同じパターンを
単一のカスタム要素として書けます:
<hc-confirm-action method="delete" action="/items/123" target="closest tr" swap="outerHTML" variant="error" message="Delete this item?" confirm-label="Delete" cancel-label="Keep"> Delete</hc-confirm-action>この要素は connectedCallback で一度だけ実行され、自身の子を上に示した
展開後の HTML に置き換えてから htmx.process(this) を呼び、htmx が新しい
属性を認識できるようにします。アップグレードは冪等です — 要素を再アタッチ
しても再展開はされません。
属性:
| 属性 | デフォルト | 対応先 |
|---|---|---|
action | (必須) | htmx リクエストの URL |
method | post | data-hx-{method} |
target | (省略) | data-hx-target |
swap | outerHTML | data-hx-swap |
variant | (省略) | ボタンの data-variant |
message | Continue? | data-hc-confirm |
title | (省略) | data-hc-confirm-title |
confirm-label | (省略) | data-hc-confirm-label |
cancel-label | (省略) | data-hc-cancel-label |
disabled-elt | this | data-hx-disabled-elt |
indicator | closest .hc-action | data-hx-indicator |
no-spinner | (真偽値) | 設定時は .hc-spinner を省略 |
マクロは任意です — ドキュメント化された契約は展開後の HTML のほうです。 マクロの属性面から外れる必要がある場合(独自のボタン内容、追加のラッパー クラスなど)は、展開後の HTML をコピーして直接編集してください。
htmx との相互作用 — 仕様
Section titled “htmx との相互作用 — 仕様”確認ゲートと htmx の間の契約を明文化します(これらはブラウザテストで 保証される事項であり、実装詳細ではありません):
- クリックはキャプチャフェーズで
preventDefault+stopPropagationによりインターセプトされます。したがって htmx (および要素上の他のクリックハンドラ)が元のアクティベーションを 観測することはありません。 - そのため
data-hx-trigger="hc:confirmed"は必須です: リクエストを 開始できるのは、ユーザーが承認したときにトリガー要素上で発火されるhc:confirmedイベントだけです。data-hc-confirmがあるのにトリガーが デフォルトのクリックのままの要素は、htmx リクエストを一切発火しません。 - キャンセルは何も発火しません。 リクエストもイベントも送信もなしです。
- 素のフォームの submit ボタン(ボタンにもフォームにも htmx の
verb 属性がない場合)は、
hc:confirmedの後にform.requestSubmit(button)で送信されます — 素のフォームを参照。どちらかに htmx verb が あればこの分岐は抑止されるため、htmx 配線済みのフォームが二重発火する ことはありません。 - htmx 自身の
hx-confirmと組み合わせないでください — htmx は アクティベーションを観測しないためhx-confirmは決して実行されず、 死んだ属性を書くことになります。1 要素につき 1 つの機構に絞ってください。 - インターセプトは document レベルで委譲されているため、htmx によって スワップインされたトリガーも再初期化なしでゲートされます。
Hyperscript による代替
Section titled “Hyperscript による代替”オーケストレーションをマークアップの隣に置きたい場合は、
installConfirm() に頼らず同じフローを
_hyperscript で表現できます。data-hc-confirm
属性を外し、確認ステップをインラインで書き、htmx がすでに待ち受けている
のと同じ hc:confirmed イベントを送ります:
<button class="hc-button" data-variant="error" type="button" data-hx-delete="/items/123" data-hx-trigger="hc:confirmed" data-hx-target="closest tr" data-hx-swap="outerHTML" _="on click if confirm('Delete this item?') send hc:confirmed to me end"> Delete</button>ここでの confirm(...) はブラウザのブロッキングプリミティブで、
スタイルが当たらず、破壊的アクションには必ずしも望ましくありません。
ページレベルの単一要素から共有の <dialog class="hc-confirm-dialog">
マークアップを再利用するには、
hyperscript インテグレーションページを
参照してください。
この形式が向くケース:
- 確認ステップが本当に一度きり(ページクローム内の削除ボタン 1 つ)で、
installConfirmをインポートしたくない。 - ほかの理由ですでに
_hyperscriptを読み込んでいる。
vanilla 側に留まるべきケース:
- 複数の要素が同じ確認フローを必要とする —
installConfirm()1 回の 呼び出しで全部カバーできます。 - マークアップを自分で書かずに、共有のスタイル済みダイアログを使いたい。
サーバレスポンス契約
Section titled “サーバレスポンス契約”サーバのレスポンスは htmx 属性が指定するとおりです — 確認ビヘイビアは リクエストの形を変えません。
行単位の削除の場合:
- 成功時: ターゲット領域の HTML を返すか、htmx が何もスワップしない
ステータスで空ボディを返します。上のレシピは最寄り
<tr>のouterHTMLをスワップするので、サーバは新しい行の HTML(行が消えた なら何も)を返します。 - トースト通知を出すには: レスポンスヘッダーに
HX-Trigger: {"hc:toast":{"message":"Deleted","variant":"success"}}を含めます。 - バリデーション失敗時: 失敗を説明する HTML を 4xx で返すか、
HX-Reswap/HX-Retargetでスワップ先を誘導します。
アクセシビリティ
Section titled “アクセシビリティ”- 共有ダイアログは標準の
<dialog>要素をshowModal()で開きます。 フォーカストラップ、Escape で閉じる、背後コンテンツのinertは ブラウザが処理します。 - ダイアログは
aria-labelledby(タイトル)とaria-describedby(メッセージ)を宣言しているので、スクリーンリーダーは開いたときに 両方を読み上げます。 - フォーカスはデフォルトで Cancel ボタンに移ります — 破壊的 アクションは、迷い込んだ Enter キーではなく明示的な承認を要求すべき です。
- 閉じるとき — 承認、キャンセル、Escape のいずれでも —
ネイティブの
dialog.close()がフォーカスを開いたトリガーへ返します。 ビヘイビア自身はフォーカスコードを追加しません。承認されたリクエストが そのトリガーを DOM から取り除く場合(テーブル行の削除が典型例)、次の フォーカス先をスワップの中に意図的に置いてください: 周辺領域を ターゲットにし、キーボードユーザーが着地すべき要素にautofocus(またはtabindex="-1"+ サーバ駆動のフォーカスヒント)を付けます。 そうしないと、ユーザーは静かに<body>に落とされてしまいます。 - 元のボタンはネイティブ
<button>のセマンティクスを保ちます。 アイコンだけでなく、見える説明的なラベル(Delete、Discard changes) を使ってください。
プログレッシブエンハンスメント
Section titled “プログレッシブエンハンスメント”ビヘイビアが読み込まれていなくても、ボタンは壊れません:
- htmx なしでは、ボタンを
<form method="post" action="/items/123/delete">の中に置いて ください — 素のフォームのパスが確認の上で 送信します。htmx 属性つきのボタンは htmx が読み込まれていなければ 何のアクションも実行しません — マークアップの記述どおりです。 hc.behaviors.jsなしでは、クリックは普通にバブリングします。htmx はhc:confirmedイベントを観測せず、何もしません。<noscript>の告知を 足すか、完全にサーバサイドの確認ページを描画するのが推奨の グレースフルデグラデーションです。
- ボタン — アクションの 視覚面。
- request-action レシピ — 確認なし・スピナーつきのバージョン。
live-searchレシピ — 同じ htmx トリガーモデルを使います。