dialog 要素と :modal 疑似クラスでモーダルを作る
Chrome 105 から :modal
擬似クラスが使用できるようになっていたので、dialog
要素を使ったモーダルを作成してみました。
dialog
要素
dialog
要素についてですが Safari の対応が 15.4 以降になるため、実際に使用する場合には polyfill が必要になることが多いかもしれません。
モーダルをアクセシブルに実装するのは、それなりに実装コストがかかります。次のページにモーダルダイアログの実装例があります。
キーボード操作や class を切り替えるための JavaScript がかなりの行数をもって記述されていることがわかります。
しかし、dialog 要素を使えばそのような記述は不要になります。
export const App = (): JSX.Element => { const ref = useRef<HTMLDialogElement>(null); return ( <> <button onClick={() => ref.current?.showModal()}>Open Modal</button> <dialog ref={ref}> <p>Hello, world!</p> <button onClick={() => ref.current?.close()}>Close Modal</button> </dialog> </> ); };
最上位のモーダルダイアログにするため、dialog.show()
ではなく dialog.showModal()
を使用します。これだけで ESC でダイアログを閉じたり、TAB の制御ができていることが確認できます。
dialog.showModal()
で表示された dialog
要素については ::backdrop
擬似要素で Scrim の色を指定することができます。
dialog::backdrop { background: rgba(128, 0, 0, 0.5); }
ただしこれだけでは、モーダルの背景のコンテンツにスクロールが発生していた場合にロックがされません。
:modal
擬似クラス
:modal
擬似クラスと :has
擬似クラスを使用することで、モーダルが表示されているときにだけスタイルを指定することが可能になります。:has
擬似クラスについては次の記事で解説しています(リンク先ではフラグを有効化する必要がありますと書いていますが、ちょうど同じタイミングの Chrome 105 でデフォルト有効化されています)。
次のコードを body
に追加することで、ダイアログが展開したときにのみ背景にあるコンテンツのスクロールを抑止できます。
body:has(dialog:modal) { touch-action: none; -webkit-overflow-scrolling: none; overflow: hidden; overscroll-behavior: none; }
こちらのコードは Prevent Scroll Chaining With Overscroll Behavior - Ahmad Shadeed を参考にさせていただいています。
気をつけるべき点
iOS Safari で背景のスクロールをロックできない問題
残念ながら上記のコードでは iOS Safari の背景のスクロールをロックできません。
もし iOS Safari でこの点が問題になるようであれば、dialog
要素を dialog.showModal()
で top-most modal dialog にするのではなく dialog.show()
を使用する必要があります。
export const App = (): JSX.Element => { const ref = useRef<HTMLDialogElement>(null); return ( <> <button onClick={() => { document.body.classList.add("open"); ref.current?.show(); }} > Open Modal </button> <dialog ref={ref}> <p>Hello, world!</p> <button onClick={() => { document.body.classList.remove("open"); ref.current?.close(); }} > Close Modal </button> </dialog> </> ); };
body.open { touch-action: none; -webkit-overflow-scrolling: none; overflow: hidden; overscroll-behavior: none; }
dialog.showModal()
を使用しなかった場合、:modal
擬似クラスと ::backdrop
擬似要素を使うことができません。上記の簡易実装では backdrop
がない実装になっているため注意してください。またキーボードの制御などもないため、制御のための実装も必要になります。
まとめ
dialog
要素と :modal
擬似クラスを使用すると、アクセシブルなモーダルダイアログが非常に簡単につくることができます。ただし iOS Safari では背景のスクロールをロックすることができないため特殊な対応が必要になります。
実際にはまだ使うには対応ブラウザを考えると難しいかもしれませんが、特定環境でのみ使用される管理画面など条件によっては使えるかもしれません。