dialog要素と自作モーダルはどっちを使うべきか
(両方の動くデモで比較)

結論

新規に業務アプリのモーダルを実装するなら、まず <dialog>showModal() を選んでください。フォーカストラップ、ESCキーでの閉じ、背景クリックの無効化、背後コンテンツの不活性化(inert)、背景の暗幕(::backdrop)が、JavaScriptをほとんど書かずに手に入ります。これらを自作で正しく実装すると意外に量が多く、抜け漏れがそのままアクセシビリティの不具合になります。

一方で、自作モーダルを選ぶ理由が残る場面もあります。開閉アニメーションを細かく作り込みたい、あるいは既存の状態管理・デザインシステムに完全に統合したいケースです。この記事では両方を動くデモで並べ、実装量とアクセシビリティで具体的に何が変わるのかを見たうえで、業務アプリでの線引きを示します。

迷ったら dialog。自作を選ぶのは「dialogでは無理な演出や統合が要る」と言い切れるときだけにする。

両方の動くデモ

下のボタンでそれぞれのモーダルを開けます。開いた状態で Tabキーを連打してフォーカスの動きと、ESCキーで閉じるかを試してみてください。ここに標準機能と自作の差がはっきり出ます。

標準機能版(dialog要素)
  • showModal() で開く
  • ESC・フォーカストラップ標準対応
  • 背景は ::backdrop

在庫を確定しますか?

Tabキーを押すと、フォーカスはこのダイアログの中だけを巡回します。ESCキーで閉じられます。

自作版(div + JS)
  • display の切り替えで開く
  • ESC・scroll-lock は自前で実装
  • フォーカストラップは未実装

自作版はこの記事のためにフォーカストラップをあえて省いています。多くの「とりあえず動く自作モーダル」がこの状態で、Tabキーを押すと背後の要素にフォーカスが抜けてしまいます。標準機能版では同じ操作でフォーカスがダイアログ内に閉じ込められます。この差が、標準機能を最初の選択肢に置く理由です。

比較表

標準機能版と自作版で、実装したときに実際に差が出る項目を並べます。とくにアクセシビリティ列は、自作だと「気づかないと抜ける」部分です。

項目 dialog要素(showModal) 自作モーダル(div + JS)
開く/閉じるの基本 showModal()close()<form method="dialog"> の送信でも閉じる クラス付け替えや display の切り替えを自分で書く
フォーカストラップ 標準で有効。Tabがダイアログ内を巡回し、外へ抜けない 自前で実装が必要。先頭・末尾でTab/Shift+Tabを折り返す処理を書く
ESCキーで閉じる 標準で有効。cancel イベントが発火する keydown を監視して自分で close 処理を呼ぶ
背後コンテンツの不活性化 開いている間、背後の要素は自動的に inert 相当になり操作できない 放置すると背後のボタンやリンクがクリック・フォーカス可能なまま残る
背景の暗幕 ::backdrop 疑似要素で指定。要素を1つ増やさずに済む オーバーレイ用の div を自分で用意してスタイルを当てる
アクセシビリティ role="dialog" / aria-modal="true" 相当の扱いが組み込み済み。フォーカス管理も含めて標準の挙動 rolearia-modal の付与、開いた直後のフォーカス移動、閉じた後に呼び出し元へフォーカスを戻す処理まで全部自分で担保する
スタイリングの自由度 中身は自由。ただし要素自体に borderpadding の初期値があるためリセットが要る 最初から素の div なので完全に自由
開閉アニメーション 可能だが display 併用の遷移に工夫が要る(後述) クラス付け替えで作りやすく、細かい演出がしやすい
ブラウザ対応 Chrome・Edge・Firefox・Safari の現行版すべてで対応済み 普通のDOM操作なので対応の心配は基本的にない
実装量の目安 マークアップ + showModal() 数行 オーバーレイ・スクロールロック・ESC・フォーカストラップ・フォーカス復帰まで含めると数十行になりがち

dialog要素がJavaScriptなしで持っている挙動

比較表の中身を、showModal() を呼んだときに実際に何が起きるかとして補足します。

element.showModal() で開くと、ダイアログはトップレイヤー(他の要素より前面)に表示され、同時に次の挙動が有効になります。フォーカスがダイアログ内に移り、Tabキーでの移動はダイアログの中だけを巡回します(フォーカストラップ)。ダイアログの外側のコンテンツは inert 相当になり、クリックもフォーカスも受け付けません。背景には ::backdrop 疑似要素が描画され、暗幕をCSSだけで指定できます。

/* showModal() で開いたときだけ表示される背景。要素を増やさずに暗幕を作れる */
dialog::backdrop {
  background: rgba(15, 23, 42, 0.55);
}

ESCキーを押すと cancel イベントが発火してダイアログが閉じます。閉じる処理を止めたい場合(未保存の入力があるときなど)は、cancel イベントで preventDefault() を呼びます。

const dialog = document.querySelector('dialog');
dialog.showModal();

// 未保存があるときだけ ESC での即時クローズを止める例
dialog.addEventListener('cancel', (e) => {
  if (hasUnsavedChanges) {
    e.preventDefault();
  }
});

閉じる操作も標準に寄せられます。ダイアログ内に <form method="dialog"> を置くと、その中のボタンで送信したときにダイアログが自動で閉じ、押したボタンの valuedialog.returnValue に入ります。上のデモの「キャンセル/確定する」はこの仕組みで、閉じるためのJavaScriptを一行も書いていません。

<dialog>
  <form method="dialog">
    <button value="cancel">キャンセル</button>
    <button value="ok">確定する</button>
  </form>
</dialog>

注意点として、<dialog> 要素にはブラウザ既定の borderpadding、中央寄せの margin が付いています。デザインを当てるときはそれらのリセットから始めます。また、暗幕(::backdrop)が出るのは showModal() で開いたときだけで、show()(非モーダル表示)では出ません。

自作モーダルで取りこぼしやすいところ

自作が「とりあえず動く」状態と「本当に使える」状態の差は、見た目ではなくキーボード操作とフォーカスに出ます。上のデモの自作版で省いたのも、まさにここです。

このサイトのモーダルダイアログ(商品詳細パネル)の事例は、自作でここまでやる、というラインを実装したものです。開いたときに閉じるボタンへフォーカスを移し、role="dialog"aria-modal="true" を付け、bodyoverflow: hidden を当ててスクロールをロックし、ESCキーの keydown を監視しています。

それでも、Tabキーを最後の要素まで送るとフォーカスがモーダルの外へ抜けます。完全なフォーカストラップと、閉じたあとに呼び出し元のボタンへフォーカスを戻す処理は、さらにコードを足す必要があります。showModal() はこの一群をまとめて肩代わりしてくれる、というのが自作との一番の差です。

自作モーダルの本当のコストは見た目ではなく、キーボードとフォーカスの面倒を最後まで見ることにある。

業務アプリでの選び方

ここまでを踏まえて、実際の判断軸を挙げます。

確認・入力・詳細表示のモーダルは dialog

「削除しますか?」の確認、フォーム入力、レコードの詳細パネル。業務アプリのモーダルはほとんどこれで、演出よりも正しく閉じる・キーボードで操作できることが優先されます。標準機能の得意分野です。

凝った開閉演出やデザインシステム統合が要るときだけ自作

スライドインやスケールなどの開閉アニメーションを細かく作り込みたい、あるいはReactなどの状態管理・既存のオーバーレイ基盤に完全に載せたい場合は自作に寄せます。ただしその場合もフォーカストラップとフォーカス復帰は必須の実装として見積もってください。

なお、dialogでもアニメーションは可能です。開くときは ::backdrop と本体に transition を当て、閉じるときは displayoverlaytransition-behavior: allow-discrete と組み合わせて遷移させます。ただ、この書き方は自作のクラス付け替えより理解のハードルが高いのは事実です。「アニメーションのためだけに自作へ倒す」なら、まずdialogでの実現可否を確認してからにするのがおすすめです。

まとめ

業務アプリのモーダルは、まず <dialog>showModal() から始めるのが妥当です。フォーカストラップ・ESC閉じ・背後の不活性化・::backdrop という、自作だと取りこぼしやすい部分を標準で持っていて、現行の主要ブラウザすべてで動きます。

自作モーダルへ倒すのは、開閉演出の作り込みや既存基盤への統合という明確な理由があるときに限ります。その場合でも、キーボード操作とフォーカス復帰まで実装して初めて標準機能と同じ土俵に立てる、という点は忘れないでください。自作でどこまで作るかの具体例は、下の動くサンプルで確認できます。