<dialog>で遊ぶ
ちょっと見ないうちに<dialog>
要素に色々な属性やAPIが生えてきたので、確認がてら遊んでみます。
この記事は主にChromeで動作を確認しています。他のブラウザでは未実装の機能もありますので、実際に使用する場合は対応状況を確認しましょう。
(2025/09/25 command
・commandfor
関連追加、toggle
イベント追加、CSS関連の項目追加・セクションを分離)
基本
<dialog>
要素はHTMLDialogElement
クラスのHTML要素です。HTMLがバージョンで呼ばれていた時代の末期に追加された要素で、長らく音沙汰のない状態が続いていましたが、ここ最近じわりと機能が追加されています。
初期状態ではdisplay: none;
が指定されており、非表示要素になっています。また、UAのスタイルとしてposition: absolute;
が指定されています。
open
属性の値によってダイアログ要素の表示・非表示の状態を切り替えることができます。
<dialog open>dialog</dialog>
ダイアログのオープン
ダイアログはopen
属性で初期から表示させることができる他に、<button>
要素のcommand
・commandfor
属性、あるいはJavaScriptで表示状態を切り替えることができます。開き方は2種類あります。
command
・commandfor
を使用した方法は最近追加された方法です。
モードレス表示
ダイアログを開いた状態でも他の操作をすることができる、いわゆる「モードレス」ダイアログとして開く方法です。HTMLDialogElement
のshow()
を呼び出すことで開くことができます。
開くと要素にopen
属性が追加され、open
を指定したときと同じ挙動になります。
<button id="button">モードレス表示</button>
<dialog id="dialog">dialog</dialog>
const dialog = document.getElementById("dialog");
document.getElementById("button").addEventListener("click", () => dialog.show());
モーダル表示
ダイアログを開いた状態では他の操作をすることができない、いわゆる「モーダル」ダイアログとして開く方法です。HTMLDialogElement
のshowModal()
を呼び出すことで開くことができます。
開くとモードレス表示と同様にopen
属性が追加されますが、要素は最上位レイヤー(#top-layer)に配置されます。またモーダル表示中は、開いたダイアログとその内部以外の要素にフォーカスできなくなります。
<button id="button">モーダル表示</button>
<dialog id="dialog">dialog</dialog>
const dialog = document.getElementById("dialog");
document.getElementById("button").addEventListener("click", () => dialog.showModal());
モーダル表示はcommand
・commandfor
属性を用いて表示させることもできます。command
にモーダル表示を実行する宣言をし、commandfor
に対象のダイアログの要素IDを指定します。JavaScriptは不要です。
<button command="show-modal" commandfor="dialog">モーダル表示</button>
<dialog id="dialog">dialog</dialog>
ダイアログを閉じる
ダイアログを閉じる方法は4パターンあります。JavaScriptで閉じる方法、<form>
要素で閉じる方法、ダイアログ要素の属性で指定する方法、<button>
要素の属性で指定する方法があります。
JavaScriptで閉じる
HTMLDialogElement
にclose()
が用意されており、実行するとダイアログを閉じることができます。
<button id="button">ダイアログを開く</button>
<dialog id="dialog"><button id="close">閉じる</button></dialog>
const dialog = document.getElementById("dialog");
document.getElementById("button").addEventListener("click", () => dialog.showModal());
document.getElementById("close").addEventListener("click", () => dialog.close());
フォームの送信として閉じる方法
ダイアログ要素内に配置された<form>
要素のメソッドにdialog
を指定することで、submit
時にダイアログを閉じることができます。ダイアログを閉じるだけで、実際のフォーム送信リクエスト(GETやPOST)は実行されません。
<button id="button">ダイアログを開く</button>
<dialog open>
<form method="dialog">
<input type="submit" value="submit">
</form>
</dialog>
formmethod
属性でも同じ結果になります。
<button id="button">ダイアログを開く</button>
<dialog open>
<form>
<input type="submit" value="submit" formmethod="dialog">
</form>
</dialog>
HTMLのclosedby属性による指定
ダイアログ要素にclosedby
属性を追加することで、ダイアログの閉じ方を指定することができます。属性値によって挙動が異なります。指定しなかった場合、モードレスとして開くとnone
、モーダルとして開くとcloserequest
が自動で設定されます。
none
標準でダイアログを閉じる方法はありません。配置された閉じるボタン等のみによって閉じることができます。
<dialog id="dialog" open closedby="none">
<button id="close">閉じる</button>
</dialog>
const dialog = document.getElementById("dialog");
document.getElementById("close").addEventListener("click", () => dialog.close());
closerequest
ブラウザが標準で提供している閉じる手段を使用することができます。多くのブラウザではEsc
キー押下でダイアログを閉じる機能を、手動でイベントを追加しなくても実現できます。
<dialog open closedby="closerequest">dialog</dialog>
any
closerequest
に加えて、Light Dismissと呼ばれるダイアログの外側をクリックした際にも閉じる指定方法です。
<dialog open closedby="any">dialog</dialog>
command / commandfor で閉じる
<form>
要素を使わずに<button>
要素とcommand
・commandfor
属性のみで閉じる方法です。
<dialog open id="dialog">
<button command="close" commandfor="dialog">閉じる</button>
</dialog>
JavaScriptでイベントを捕捉する
ダイアログ要素では、通常のイベントに加えてclose
とcancel
の2種類のイベントが存在します。
closeイベント
ダイアログを閉じたタイミングで発火されます。
<div id="output"></div>
<dialog id="dialog" open>
<button id="close">閉じる</button>
</dialog>
const dialog = document.getElementById("dialog");
const output = document.getElementById("output");
dialog.addEventListener("close", () => output.textContent = "close");
document.getElementById("close").addEventListener("click", () => dialog.close());
cancelイベント
ダイアログ要素のrequestClose()
を実行した場合、command
属性にrequest-close
を指定した場合、もしくはブラウザが標準で提供している閉じる手段を用いた場合に発火されます。closedby
属性に指定する値とは単語の順番が逆です。
cancel
イベント発火後にclose()
が実行されます。cancelable
なイベントであるため、e.preventDefault()
を実行することでダイアログを閉じる動作を抑制することができます。
<button id="button">モーダル表示</button>
<div id="output"></div>
<dialog id="dialog">
<button id="close">close()</button>
<button id="requestclose">requestClose()</button>
</dialog>
const dialog = document.getElementById("dialog");
document.getElementById("button").addEventListener("click", () => dialog.showModal());
document.getElementById("close").addEventListener("click", () => dialog.close());
document.getElementById("requestclose").addEventListener("click", () => dialog.requestClose());
const output = document.getElementById("output");
dialog.addEventListener("close", () => output.textContent += " close");
dialog.addEventListener("cancel", () => output.textContent += " cancel");
なお、ダイアログ表示後にユーザが何も操作をせずにEscキーで閉じた場合は発火されません。例えば、画面表示時にモーダルダイアログを表示させ、画面内のどこかをクリックしたりキーボードを押下したりせずにEscキーを押下した場合、cancel
イベントが発火することなくダイアログが閉じます。
Chromium系の挙動
Chromium系ではcancel
イベントの挙動が一部異なります。
- モーダル表示状態でEscキーを2回連続で押下した際の2回目の
cancel
イベントはキャンセル不可(cancelable=false
)
仕様なのかバグなのかは不明です。issueを見る限りでは意図的のように見えますが、実態はよくわかりません。
toggleイベント
<dialog>
要素の表示状態が切り替わった際に発火されます。
<button id="button">モーダル表示</button>
<div id="output"></div>
<dialog id="dialog">
<form>
<input type="submit" value="submit" formmethod="dialog">
</form>
</dialog>
const dialog = document.getElementById("dialog");
document.getElementById("button").addEventListener("click", () => dialog.showModal());
const output = document.getElementById("output");
dialog.addEventListener("toggle", e => output.textContent += " " + e.newState);
CSSによるスタイリング
<dialog>
要素専用ではありませんが、便利なCSSがいくつか存在します。
開いているdialog要素に適用
<dialog>
要素は、自身のdisplay
プロパティを切り替えるため、閉じている状態の<dialog>
要素にdisplay: none;
以外を指定すると使い物にならなくなります。開いている状態を指定する方法は2種類あります。
[open]
<dialog>
要素を開いている状態ではopen
属性が指定されるため、[open]
属性セレクタを使用してスタイルを当てる方法です。
<dialog open>
<div>A</div>
<div>B</div>
<div>C</div>
</dialog>
dialog[open] {
display: flex;
gap: 10px;
}
:open
open状態は:open
擬似クラスを用いて判定することもできます。
<dialog open>
<div>A</div>
<div>B</div>
<div>C</div>
</dialog>
dialog:open {
display: flex;
gap: 10px;
}
モーダル表示時の背景
モーダル表示時の背景は::backdrop
疑似要素でスタイルすることができます。
<button id="button">モーダル表示</button>
<dialog id="dialog">dialog</dialog>
#dialog::backdrop {
background-color: #0008;
backdrop-filter: blur(5px);
}
const dialog = document.getElementById("dialog");
document.getElementById("button").addEventListener("click", () => dialog.showModal());
ちなみに::backdrop
疑似要素は最上位レイヤー(#top-layer)に配置されている要素にのみ適用されます。モーダルダイアログ以外ではPopover
で使えます(そちらでは使う機会は滅多に無さそうですが)。
モーダル表示時のみのスタイル
:modal
擬似クラスを用いることで、モーダル表示した際のみスタイルを適用することができます。
<button id="show">モードレス表示</button>
<button id="show-modal">モーダル表示</button>
<dialog id="dialog">
<form>
<input type="submit" value="submit" formmethod="dialog">
</form>
</dialog>
#dialog:modal {
border-color: #f00;
}
const dialog = document.getElementById("dialog");
document.getElementById("show").addEventListener("click", () => dialog.show());
document.getElementById("show-modal").addEventListener("click", () => dialog.showModal());
ダイアログから値を渡す
close()
あるいはrequestClose()
の引数に値をセットした場合、ダイアログのreturnValue
プロパティに値が格納されます。渡せるものはstring
のみです。
<button id="button">モーダル表示</button>
<div id="output"></div>
<dialog id="dialog">
<button id="close">close("1")</button>
<button id="requestclose">requestClose("2")</button>
</dialog>
const dialog = document.getElementById("dialog");
document.getElementById("button").addEventListener("click", () => dialog.showModal());
document.getElementById("close").addEventListener("click", () => dialog.close("1"));
document.getElementById("requestclose").addEventListener("click", () => dialog.requestClose("2"));
const output = document.getElementById("output");
dialog.addEventListener("close", () => output.textContent = dialog.returnValue);
実用?サンプル
とりあえず、すぐに思いついたサンプルを1つ。実用性があるかどうかは知りません。
確認ダイアログ
requestClose()
のおかげで、だいぶスッキリ書けています。Promise.withResolvers()
を使っているからスッキリ書けているとか言わない。
<button id="button">確認ダイアログを表示</button>
<div id="output"></div>
const confirmDialog = async ({ msg }) => {
const dialog = document.createElement("dialog");
const message = document.createElement("p");
message.textContent = msg;
dialog.appendChild(message);
const accept = document.createElement("button");
accept.textContent = "はい";
accept.addEventListener("click", () => dialog.close("1"));
dialog.appendChild(accept);
const decline = document.createElement("button");
decline.textContent = "いいえ";
decline.addEventListener("click", () => dialog.close("2"));
dialog.appendChild(decline);
const cancel = document.createElement("button");
cancel.textContent = "キャンセル";
cancel.addEventListener("click", () => dialog.requestClose());
dialog.appendChild(cancel);
const { promise, resolve, reject } = Promise.withResolvers();
dialog.addEventListener("close", () => resolve(dialog.returnValue));
dialog.addEventListener("cancel", reject);
document.body.appendChild(dialog);
dialog.showModal();
return promise;
};
const output = document.getElementById("output");
document.getElementById("button").addEventListener("click", async () => {
try {
const result = await confirmDialog({ msg: "よろしいですか?" });
output.textContent = `${result}→処理を続行します...`;
} catch {
output.textContent = "キャンセルされました";
}
});
その他
記事内のWeb Featuresのステータスは、以下のスクリプトを用いて表示しています。
( 更新)
カテゴリ: JavaScript