HTML/CSS
投稿日:

このデザイン、どうコーディングする? #6|モーダルを外側クリックで閉じる?JS最小限でもしっかり動く&アクセシブルに設計する方法

このデザイン、どうコーディングする? #6|モーダルを外側クリックで閉じる?JS最小限でもしっかり動く&アクセシブルに設計する方法

モーダルは「見た目」より「ふるまい」が大事

モーダルダイアログで、「外側をクリックしたら閉じるようにしてほしい」と言われること、私も現場で何度も経験しています。
確かに、見た目を整えるのは簡単。でも、“どう閉じるか”や“どこにフォーカスを当てるか”といったふるまいをちゃんと設計するのは、思っている以上に繊細な作業です。

特に最近はアクセシビリティ(a11y)要件が求められる場面も増えてきており、
とりあえず閉じられればOK」では通用しないことも多くなってきました。

そこで今回は、私が実務で辿り着いた結論――
「最小限のJSで、しっかり閉じられて、きちんと操作できるモーダル」の実装方法を紹介します。

結論:<dialog> 要素+最小限JSが、今のモーダルの最適解

現在のブラウザ環境(2025年)において、モーダルの実装で最もバランスが良いのは、
HTML標準で用意されている <dialog> 要素を使う方法です。

<dialog> の特長

  • <dialog> 要素を dialog.showModal() で開き、.close() で閉じるだけ
  • 外側のクリックや ESC キーでの閉じ動作がデフォルトで組み込まれている
  • キーボードフォーカスも自動でモーダル内に閉じ込められる(フォーカストラップ)
  • role="dialog"aria-modal="true" などのアクセシビリティ属性も基本的に不要(自動で付与)

これだけで、モーダルに必要な挙動がほぼ標準でまかなえるため、
「JavaScriptを少なく、でもちゃんと動くものを作りたい」という現場には最適です。

実務での判断ポイント

要件<dialog> は対応済み?
外側クリックで閉じたい✅ デフォルト対応
ESCキーで閉じたい✅ 自動で反応
フォーカス制御したい✅ 自動でフォーカストラップ
スクリーンリーダー対応したい✅ 自動でロール付与&フォーカス移動

こうした機能がほぼすべて「何もしなくても効く」というのは、モーダル実装の工数を大きく削減します。
私自身も、現在では モーダルはまず <dialog> ベースで組んでみて、足りなければ拡張するというアプローチが定番になりました。

<dialog> 要素の基本構成と使い方

HTML5で追加された <dialog> 要素は、モーダルダイアログを“ネイティブに”実装できる要素です。
従来のように div を絶対配置し、JavaScriptで開閉・フォーカス制御を一から書く必要はありません。

ここでは、<dialog> の基本的な構成と開閉方法を実装例とともに解説します。

各要素の意味

要素解説
<dialog>モーダル本体。閉じているとDOM上には表示されない
<form method="dialog">中に入れることで、<button> を押したときに自動的に閉じる
<button>特別な属性なしでも「閉じるボタン」として機能する
<button id="openBtn">JavaScriptでモーダルを開くトリガー

JavaScriptでの開閉制御 ー showModal() vs show()

  • .showModal() は背景をロックして表示(モーダル表示
  • .show() は背景をロックせず、インライン表示(ツールチップのような用途向け)

ほとんどのケースでは .showModal() を使います。

CSSでモーダルの見た目を整える

  • dialog::backdrop<dialog> 固有の疑似要素で、背景の半透明オーバーレイに使えます
  • スタイリングは通常の要素と同様に可能(※ただし display: flex 等が必要な場合も)

注意点:form がないと「閉じるボタン」が機能しない

これは意外とハマりがちなポイントです。
モーダル内の「閉じる」ボタンは、<form method="dialog"> の中にあることで、自動的に .close() が実行されます。

ボタンだけで閉じたい場合は、明示的に .close() を呼ぶ必要があります。

利用可能ブラウザ(2025年現在)

ブラウザ対応状況
Chrome / Edge✅ 完全対応
Firefox✅ 対応済み(v98〜)
Safari(iOS含む)✅ 安定対応(v15.4〜)

主要ブラウザはすべて対応済みのため、実務導入も安心です。

このように <dialog> を使えば、最低限のコードでモーダルの基本機能がほぼ実装済みの状態からスタートできるというのが大きな魅力です。

次は、外側クリックや ESC キーでの自動的な閉じ動作について、具体的に見ていきましょう。

外側クリックや ESCキーによる閉じ方の挙動

モーダルを開いたあと、ユーザーが背景部分(モーダルの外側)をクリックしたときに閉じるかどうかは、ユーザー体験としてとても重要です。
<dialog> 要素は、このあたりも最初から「いい感じ」に動作する設計になっています。

外側クリックで閉じるのはデフォルト仕様

<dialog>.showModal() で開くと、背景の ::backdrop が自動的に生成されます。
この backdrop をクリックすると、何も書かなくても自動で .close() が呼び出されます。

つまり、以下のようなコードだけで外側クリック → モーダル閉じるが実現します。

dialog.showModal(); // これだけでOK

追加のイベントリスナー不要。すでに組み込まれているのです。

外側クリックで閉じられないようにしたい場合は?

もし、モーダル内の操作が必須(入力完了まで閉じられない等)である場合、
以下のように cancel イベントでキャンセルを抑止できます。

dialog.addEventListener('cancel', (event) => {
  event.preventDefault(); // ESCキーも無効化される
});

このとき、外側クリックもESCキーも効かなくなるので、
明示的に「閉じる」ボタンや処理を用意することが必須になります。

cancel イベントとは?

  • モーダルを ESCキーで閉じようとしたときに発火します
  • preventDefault() を使うと閉じ動作をキャンセルできます
  • 外側クリックと併せて制御したいときにも利用されます

よくある誤解:「クリックイベントで外を判定しないとダメ?」

旧来のカスタムモーダル(div.modal + JS)では、背景を addEventListener('click', ...) で監視して、
「クリック対象が .modal-content でなければ閉じる」みたいなロジックを自前で書く必要がありました。

ですが、<dialog> を使えばその必要は一切ありません。
開く・閉じるの基本動作は 標準実装でカバーされているのです。

フォールバックとして details + summary を使う場合の挙動

一部のレガシー環境やJSレス環境では、
<dialog> が使えないケースに備えて、以下のような代替パターンも存在します。

<details>
  <summary>開く</summary>
  <div class="modal">モーダルの中身</div>
</details>

ただしこれは:

  • 外側クリックで閉じられない
  • ESCキーで閉じられない
  • フォーカスも閉じ込められない

といった制約があるため、あくまで簡易UIや静的ページ向けです。

このように、外側クリックやESC対応が“標準動作”として備わっているのが、<dialog> 最大の魅力の一つです。

次は、フォーカストラップやARIA属性を含めた、アクセシブルなモーダル設計のポイントを解説していきます。

キーボードフォーカス制御とアクセシビリティ対応

モーダルのUIは、見た目だけでなく「操作中のユーザーが今どこにいるか」を明確にすることが非常に重要です。
特にキーボード操作やスクリーンリーダー利用者にとっては、フォーカスの管理と適切なARIA属性の設計が不可欠です。

<dialog> 要素を使うことで、こうした要件の多くを標準でカバーできます。

自動でフォーカストラップされる

<dialog>.showModal() で開くと、開いた時点でフォーカスがモーダル内の最初のフォーカス可能要素に自動で移動します。

さらに、その状態ではTabキーでの移動もモーダル内に限定される(いわゆる「フォーカストラップ」)ため、
ユーザーがモーダル外にフォーカスを移してしまう心配もありません。

補足:明示的に移動先を指定したい場合

dialog.addEventListener('shown', () => {
  dialog.querySelector('input')?.focus();
});

shown イベントは未標準なので、代わりに DOMContentLoaded やカスタム処理で代替することもあります。

<dialog> に必要なARIA属性は?

基本的には不要です。モダンブラウザは、<dialog> に対して自動的に以下を付与します:

  • role="dialog"
  • aria-modal="true"

とはいえ、補足的に以下を付けておくと、より確実にアクセシブルになります。

<dialog aria-labelledby="dialog-title" aria-describedby="dialog-desc">
  <h2 id="dialog-title">お知らせ</h2>
  <p id="dialog-desc">この操作を完了するには…</p>
  ...
</dialog>
  • aria-labelledby:モーダルの「タイトル」と認識される要素を指定
  • aria-describedby:補足説明として読み上げさせる要素

JSカスタムモーダルで忘れられがちなこと

手動でモーダルを実装する場合、以下のことを自前で制御しなければならないため、
実は非常に手間がかかります:

要件<dialog>カスタム実装
初期フォーカス移動✅ 自動❌ 要自作
フォーカストラップ✅ 自動❌ 要ループ処理
role/aria属性✅ 自動 or 補助のみ❌ 明示指定が必須
ESCキーで閉じる✅ 自動❌ 要イベント処理

実務的なアプローチ

「アクセシブルなモーダルを簡単に実装したい」ときは、

  • まず <dialog> で構築
  • 必要に応じて aria-labelledby を追加
  • フォーカス移動やESC無効化などを JavaScript で補強

という流れが最小コストで最大の品質を出す方法です。

次章では、<dialog> を使わずにCSSだけ or JS最小限で実装する代替パターンについて、現実的な選択肢を紹介します。

JSレスまたは限りなくJSを減らす方法(代替手段)

「できるだけJavaScriptを使いたくない」「完全な静的サイトで動かしたい」
そんな場面でも、モーダルっぽいUIをCSSだけである程度再現する方法は存在します。

ここでは、実務でもたまに役立つ、JS最小限 or JSレスのモーダル代替手段を紹介します。
ただし制約もあるため、「何ができて、何ができないか」も併せて解説します。

🅰 details + summary を使った“折りたたみ型モーダル”

✅ 特徴

  • 開閉が 完全にHTML+CSSだけで完結
  • open 属性が付与され、スタイル切り替え可能

❌ 注意点

  • 外側クリックでは閉じられない
  • ESCキーは効かない
  • フォーカストラップがない
  • スクリーンリーダー対応は限定的

これはあくまで、軽量なトグルUIや“なんちゃってモーダル”として限定利用するのが現実的です。

🅱 :target 擬似クラスを使ったアンカー遷移型モーダル

✅ 特徴

  • JavaScript完全不要
  • 擬似的な表示/非表示切り替えが可能

❌ 注意点

  • URLの #ハッシュ を書き換える必要がある
  • 複数モーダルの管理が難しい
  • ESCキー・フォーカストラップ非対応
  • 閉じるための a href="#close" というやや強引な方法が必要

どんな場面で使えるか?

方法適しているケース
details + summary静的なFAQ、軽量な説明文ポップアップなど
:target1ページ完結のキャンペーンLP、完全静的な案内ページなど

実務での結論

「モーダルとしてのふるまい(閉じる、フォーカス、キーボード対応)を求める」場合は、
やはり <dialog> を使うのが最も安全かつ実装コストも低い選択肢です。

それでも、プロジェクトによっては「JSを使えない」「ビルド不要で完結したい」などの要件もあるため、
これらのCSSベースの手法も知っておくと役立つ場面はあります

次章では、<dialog> を使った実践的なサンプルコードを紹介します。
モーダルの開閉、スタイル、外側クリック対応、すべて含んだ現場向けの構成です。

サンプルコード:<dialog>ベース+ESC&外側クリック対応

ここまで紹介してきた <dialog> 要素は、標準のHTMLタグ+最小限のJavaScriptで、実用的なモーダルを構築できる優秀な選択肢です。

このセクションでは、以下の要素をすべて含んだ実装例を紹介します:

  • <dialog> を使ったモーダル構造
  • 開く/閉じるボタン
  • 外側クリック、ESCキーでの自動閉じ
  • スタイリングされた背景オーバーレイと内容配置
  • 必要最低限のアクセシビリティ対応
  • dialog::backdrop により、自動生成されるオーバーレイ背景に半透明の黒を設定
  • width: min(90vw, 500px) で、モバイルでも破綻しない中央配置を実現

この実装のポイントまとめ

機能対応説明
モーダル表示.showModal() で表示
モーダル非表示<button> + form method="dialog"
外側クリックで閉じる<dialog> 標準動作
ESCキーで閉じる自動対応
フォーカストラップ自動対応
ARIA属性必要に応じて補足的に追加可能(aria-labelledby など)

この構成は、初期構築が早い・保守が簡単・アクセシビリティも標準対応という3拍子揃った、現代のモーダル実装の“型”です。
このままベースにして、内容やデザインを調整していけば、ほとんどの案件に対応できます。

次は、今回の内容を踏まえたシンプルなまとめと、モーダルUIに対する設計方針の再確認を行います。

まとめ:モーダルは“閉じ方”で信頼性が決まる

モーダルというUIは、見た目よりも「どう閉じるか」「どう操作を制御するか」が本質です。

今回紹介した <dialog> 要素を使えば、以下のような重要な機能がHTML+数行のJSだけで実現できます:

  • 外側クリックやESCキーで自然に閉じる
  • キーボード操作をモーダル内に限定(フォーカストラップ)
  • 標準でアクセシビリティ対応済み(role, aria-modal など)

特別なJSライブラリを使わなくても、十分実用に耐えるモーダルが“標準技術”で構築できる時代になりました。

一方、details:target を使ったCSSベースの代替手法もありますが、閉じ方やフォーカス管理まで含めて“モーダルらしさ”を求めるなら、やはり <dialog> がベストです。

要件に応じて「どこまで作り込むか」を選べることが、今のフロントエンドに求められる柔軟さだと思います。

次回は、縦書き対応デザインをレスポンシブで切り替える方法について解説します。
writing-modelogical properties を使いながら、横書き/縦書きをスマートに切り替えるUI設計に挑みます。

次回もお楽しみに!