コンテンツにスキップ

ダイアログ

hc-dialog は、標準の <dialog> 要素に適用する薄いクラスです。 モーダルのライフサイクル — フォーカストラップ、Escape で閉じる、 背景の inert、トップレイヤー描画 — はブラウザが所有し、この コンポーネントは見た目の面だけを足します。

別名: モーダル、モーダルダイアログ(modal)。

Confirm something

Body content.

パーツ 用途
.hc-dialog ルートの <dialog> 要素。
.hc-dialog__header タイトル行。下に区切り線。
.hc-dialog__title ヘッダー内の見出し。
.hc-dialog__body メインのコンテンツ領域。ダイアログがビューポートより高いときスクロールするのはここ。
.hc-dialog__footer アクション行。右寄せ、上に区切り線。

パーツは慣習です — どれでも省略できます。

開いているダイアログはカラムなので、ビューポートより高い内容は 本文の中でスクロールし、ヘッダーとフッターはその場に留まります。 主要アクションはフッターにあるためこれが効きます — フィールドが十数個 ある検索パネルでは、そうしないとボタンが画面外に押し出されます。

3 つのパーツをまとめて 1 つのフォームで包んでも構いません。フッターの ボタンが本文のフィールドを送信する場合の通常の形です:

<dialog class="hc-dialog" id="filters" aria-labelledby="filters-title">
<form method="get" action="/orders">
<div class="hc-dialog__header">
<h2 class="hc-dialog__title" id="filters-title">検索条件</h2>
</div>
<div class="hc-dialog__body">…多数のフィールド…</div>
<div class="hc-dialog__footer">
<button class="hc-button" type="submit" data-variant="primary">検索</button>
</div>
</form>
</dialog>

横幅の広いパネルには --hc-dialog-max-width で余裕を与えてください。 高さの上限はブラウザが持つので、指定は不要です。

<dialog> にはクリックで開くための属性が自前にはありませんが、HTML の invoker commands がどのボタンにもそれを与えます — 宣言的で、 スクリプトもリスナーも不要、Content-Security-Policy: default-src 'self' が文句を言うものもありません:

<button class="hc-button" type="button"
commandfor="my-dialog" command="show-modal">Open</button>
<dialog class="hc-dialog" id="my-dialog">
…
<button class="hc-button" type="button"
commandfor="my-dialog" command="close">Close</button>
</dialog>
  • command="show-modal" は commandfor が id で指す要素の showModal() を呼びます — フォーカストラップ、::backdrop、Escape、 閉じたときにオープナーへフォーカスが戻ることまで、すべてブラウザの 仕事です。
  • command="close" は close() を呼びます。どこからでも効き、 htmx フォームの内側でも動きます。そこでは formmethod="dialog" のボタンは htmx に捕まってダイアログに届きません (remote-dialog レシピ が Cancel を別の <form method="dialog"> に置いているのはそのためです — どちらの書き方でも構いません)。
  • どちらも type="button" が必要です。submit ボタンは command を 無視してフォームを送信します。

非モーダルの show() や、戻り値つきで閉じるにはネイティブのメソッドを 使います:

dialog.showModal(); // modal, with focus trap and ::backdrop
dialog.show(); // non-modal, no backdrop
dialog.close(); // close (modal or non-modal)
dialog.close('confirm'); // close and set returnValue

解散に反応するには close イベントを待ち受けます:

dialog.addEventListener('close', () => {
if (dialog.returnValue === 'confirm') {
// user accepted
}
});

本文がサーバから来るモーダルは リモートダイアログレシピを 参照。事前の確認ステップ(承認までラウンドトリップなし)は 確認アクションを 参照してください。

ダイアログ内のフォームを成功時に閉じるには、 data-hc-close-dialog-on-success でオプトインします:

<form
data-hx-post="/items"
data-hx-target="closest dialog"
data-hx-swap="outerHTML"
data-hc-close-dialog-on-success>
…
</form>

この糊付けは installCloseDialog() ビヘイビアです — htmx:afterRequest を待ち受けて成功を確認し、最寄りの <dialog> 祖先の dialog.close() を呼びます。installCloseDialog() は冪等で、 アンインストーラを返します。ゼロ設定の @hypermedia-components/core/behaviors エントリが自動インストールし、 htmx でスワップされた内容も自動で拾います。

  • ネイティブの <dialog> 要素を使ってください。showModal() を 本当に使えないのでない限り、<div role="dialog"> でダイアログを 模倣しないでください。
  • showModal()(または command="show-modal")で開かれたダイアログ では、フォーカストラップと Escape をブラウザが自動処理し、閉じると オープナーにフォーカスを戻します。
  • ダイアログにラベルを付けてください。次のいずれかで:
    • ダイアログにタイトルの id を指す aria-labelledby を設定する、 または
    • タイトルを最初のフォーカス可能要素より前に置き、開いたときに 読み上げられるようにする。
  • 破壊的な確認では、開いたときに Cancel ボタンへフォーカスして ください — 破壊的アクションへは決して。
トークンパス 用途
dialog.bg / -fg 面の色。
dialog.border ダイアログ周りの枠線。
dialog.radius 角丸。
dialog.padding 各パーツのパディング。
dialog.gap フッターのボタン間のギャップ。
dialog.max-width 最大インラインサイズ。
dialog.backdrop ::backdrop の色。
dialog.duration 開閉トランジションの時間。
生成される CSS 変数を表示
--hc-dialog-bg | -fg | -border
--hc-dialog-radius | -padding | -gap | -max-width | -duration
--hc-dialog-backdrop

レシピでの利用: リモートダイアログ · セッション切れ再認証