`<dialog>`: ダイアログ要素
(developer.mozilla.org)- HTML `` 要素は、ブラウザネイティブのモーダル・非モーダルダイアログを作成し、モーダルではページの他の領域を
inert状態にして操作をブロックする - 開閉方法の基本は
showModal()、show()、close()で、``、Invoker Commands API、Popover API による宣言的な制御も可能 closedby属性は、ユーザーがダイアログを閉じられる経路をany、closerequest、noneに分け、開き方によってデフォルト動作が異なる- アクセシビリティでは初期フォーカスと明示的な閉じるボタンが重要で、
showModal()で開いたダイアログはデフォルトで最初のフォーカス可能要素へのフォーカスと Esc キーでのクローズを提供する - スタイリングでは
:modal、:open、::backdropを活用し、アニメーションにはdisplay、overlay、@starting-style、transition-behavior: allow-discreteなどの離散的なトランジション処理が必要
`` の役割とモーダル動作
- HTML `` 要素は、モーダルおよび非モーダルダイアログを作成するための要素
- モーダルダイアログは他の UI 要素との相互作用を防ぎ、ページの残りの部分を
inert状態にする - 非モーダルダイアログは、開いている状態でもページの他の部分と引き続き相互作用できる
開いた状態と閉じるポリシー
- `` はグローバル属性を含むが、
tabindex属性は使用してはいけない- `` 自体はインタラクティブな要素ではなく、フォーカスを受けない
- 内部コンテンツと閉じるボタンはフォーカスを受け、操作できる
open属性は、ダイアログがアクティブで操作可能であることを示すopen属性がない場合、ユーザーには表示されない- ダイアログを表示する際は、
open属性より.show()または.showModal()の使用が推奨される open属性で開かれた `` は非モーダル- 非モーダルダイアログの開閉状態を
open属性のトグルで変更することはできるが、推奨されない
closedby属性は、ユーザーがどの方法でダイアログを閉じられるかを指定するany: 3 つの方法すべてで閉じられる- ダイアログ外のクリックやタップなどの light dismiss
- Esc キー、モバイルの戻る操作、dismiss ジェスチャーなどのプラットフォーム固有の動作
HTMLDialogElement.close()を呼び出すボタンや `` の送信など、開発者が指定した方法
closerequest: プラットフォーム固有の動作と開発者指定の方法で閉じられるnone: 開発者指定の方法でのみ閉じられる- 有効な
closedby値がない場合、showModal()で開かれたときはcloserequestのように、それ以外ではnoneのように動作する
JavaScript と宣言的制御
- JavaScript で `` の表示とクローズを直接制御できる
showModal(): モーダルダイアログを表示show(): 非モーダルダイアログを表示close(): ダイアログを閉じる内のをmethod="dialog"で送信して閉じることもできる- モーダルダイアログは Esc キーでも閉じられる
- Invoker Commands API は、ボタン属性だけでモーダルダイアログを開閉できるようにする
- `` に
commandforとcommand属性を指定する - ダイアログで使用できるコマンドは
"show-modal"、"close"、"request-close"
- `` に
Open dialog
This dialog was opened using an invoker command.
Close
- Popover API は、非モーダルダイアログを宣言的に開閉・トグルできるようにする
- `` に
popover属性を追加してポップオーバーにする - ボタンや入力要素に
popovertarget、popovertargetactionを指定する - ポップオーバーダイアログは非モーダルなので、外側をクリックして閉じられる
popover値を指定しない場合はデフォルト値"auto"が使われ、light dismiss が有効になるpopover="manual"は light dismiss を無効にし、閉じるボタンなど別の方法が必要になるpopovertargetactionを省略するとデフォルト値toggleが使われる
- `` に
フォーム送信とクローズ処理
- すべての `` には閉じるメカニズムが必要であり、物理キーボードのないデバイスでも動作しなければならない
- 閉じる方法には複数ある
内のにmethod="dialog"を設定して送信する- light dismiss が有効な状態でダイアログ外をクリックする
- 許可されたダイアログで Esc キーを押す
HTMLDialogElement.close()を呼び出す
- `` または送信ボタンの
formmethod="dialog"はダイアログを閉じる- フォームコントロールの状態は保存されるが、送信はされない
returnValueはアクティブなボタンの値に設定される
- 必須入力値のあるフォームでは、ユーザーエージェントは値が提供されるまで通常送信によるダイアログのクローズを防ぐ
- 閉じるボタンに
formnovalidateを使うとフォーム検証を回避できる - JavaScript で
dialog.close()を呼び出して閉じることもできる
- 閉じるボタンに
フォーカスとアクセシビリティ
showModal()で `` を開くと、デフォルトで最初のネストされたフォーカス可能要素にフォーカスが設定される- 特定のダイアログで最も適切な初期フォーカス位置を指定するには
autofocus属性を使える- すぐに操作する要素がない場合は、閉じるボタンに
autofocusを置くことが推奨される - 動的にレンダリングされるダイアログのように初期フォーカス位置が不確かな場合は、`` 自体が適切な初期フォーカス位置になることがある
- すぐに操作する要素がない場合は、閉じるボタンに
- すべてのユーザーが閉じられるよう、確認・キャンセル・閉じるボタンなどの 明示的なボタン を含める方法が最も堅牢
showModal()で呼び出されたダイアログはデフォルトで Esc キーで閉じる- 非モーダルダイアログはデフォルトでは Esc キーで閉じない
- キーボードユーザーはモーダルダイアログが Esc キーで閉じることを期待する
- 複数のモーダルダイアログが開いている場合、Esc キーは最後に表示されたダイアログだけを閉じるべきで、`` を使うとブラウザがこの動作を提供する
- ネイティブの `` は、他の要素で作られたカスタムダイアログで手動実装しなければならないユーザビリティおよびアクセシビリティ機能を提供する
- ブラウザは `` を ARIA
role="dialog"を使用するカスタムダイアログに近い形で公開するshowModal()で呼び出された `` は暗黙的にaria-modal="true"を持つshow()、open属性、デフォルトのdisplay変更で表示された `` はaria-modal="false"として公開される- モーダルダイアログ実装では、`` とそのコンテンツ以外のすべてが
inertとしてレンダリングされるべきであり、showModal()使用時にはブラウザがこの動作を提供する
CSS スタイリングと backdrop
- `` は通常の要素と同様に要素名で選択できる
- 状態ベースのスタイリングには
:modal、:open擬似クラスを使用できる - モーダルダイアログの背景は
::backdrop擬似要素でスタイリングできるshowModal()でダイアログを表示したとき、`` の背後に現れる backdrop に適用されるinert状態にある背後のコンテンツをぼかしたり、暗くしたり、覆い隠したりできる
例で見る使用パターン
- Invoker Commands API の例では、
command="show-modal"ボタンでダイアログを開き、command="close"ボタンで閉じる- “Open dialog” ボタンで開ける
- “Close” ボタンまたは Esc キーで閉じられる
- Popover API の例では、
popover、popovertarget、popovertargetactionで非モーダルダイアログを開閉する- “Close” ボタン、Esc キー、ダイアログ外の選択で閉じられる
popover="manual"を使うと light dismiss は無効になる
open属性の例では、ページ読み込み時点ですでに開いている HTML のみの非モーダルダイアログを作る- `` の “OK” ボタンで閉じられる
- 閉じた後に再び開く方法は提供されない
- 非モーダルダイアログの表示には
HTMLDialogElement.show()の使用が好ましい
- モーダルダイアログの例では、
.showModal()で開きclose()で閉じる::backdropでグラデーション背景をスタイリングする- ダイアログが開いている間、ダイアログ外部は
inert状態になり、ドキュメントと相互作用できない
returnValueの例では、フォームとボタン値でダイアログの戻り値を扱う- デフォルトの
returnValueは空文字列、またはダイアログ内フォームを送信したボタンの値 - “Confirm” ボタンは選択値を
close()に渡して戻り値として使用する - Esc キーで閉じると
returnValueは更新されず、closeイベントも発生しないため、`` テキストは更新されない
- デフォルトの
`` アニメーション
- 非表示の
は `display: none`、表示中のはdisplay: blockになる - 表示状態が変わるとき、top layer とアクセシビリティツリーに追加または削除される
- `` をアニメーションさせるには
displayプロパティがアニメーション可能である必要がある- 対応ブラウザでは
displayを離散アニメーションとして扱う noneからblockに変わるときは 0% 時点でblockに切り替わり、アニメーション全体を通して表示されるblockからnoneに変わるときは 100% 時点でnoneに切り替わり、アニメーション全体を通して表示される
- 対応ブラウザでは
-
CSS transition によるアニメーション
- CSS transition で `` をアニメーションさせるには、次の機能が必要
@starting-style: ダイアログが開くたびにトランジション開始値を提供するdisplaytransition: トランジション中、ダイアログを表示するdisplay値を維持するoverlaytransition: top layer からの削除をトランジション完了まで遅延させるtransition-behavior: allow-discrete: 通常はアニメーションしないdisplay、overlayの離散トランジションを有効化する:open擬似クラスをサポートしないブラウザでは、dialog[open]属性セレクターで開いた状態をスタイリングできる- `` は表示されるたびに
display: noneからdisplay: blockに変わるため、各エントリトランジションで@starting-styleからdialog:openスタイルへ遷移する
-
keyframe アニメーション
- CSS keyframe アニメーションは transition とは異なる制約を持つ
@starting-styleは提供しないdisplay値を keyframe 内に含めるallow-discreteを明示的に有効化する必要はないoverlayを keyframe に設定する必要もない- backdrop のフェードアウトは、ダイアログが閉じるときに backdrop が DOM から即座に削除されるためアニメーションできない
技術要約
- コンテンツカテゴリ: flow content、sectioning root
- 許可されるコンテンツ: flow content
- 開始タグと終了タグはいずれも必須で、タグ省略はない
- 許可される親: flow content を受け入れるすべての要素
- 暗黙的 ARIA ロール:
dialog - 許可される ARIA ロール:
alertdialog - DOM インターフェース:
HTMLDialogElement - 仕様: HTML # the-dialog-element
1件のコメント
Hacker Newsのコメント
インタラクティブなHTML要素には、ファイルピッカー、カラーピッカー、日付/時刻ピッカー、数値スライダー、テキストフィールドの候補オプション、展開可能な要約/詳細、FAQ、コントロール付きメディアプレーヤーのようなものがある
こうした要素は軽量でセマンティックなのがよく、同じ
nameを共有する<details>では、1つを開くと前の項目が閉じる挙動も可能もっとよく実装されていてほしい
2019年から**
<dialog>**を使っているが、FirefoxとSafariがさらに数年対応していなかった時期でも、Googleのpolyfillの品質が高く、業務用SaaSの本番環境で問題なく使えた一番残念だったのは、この要素がほとんどスタイルされていなかったこと。非常に基本的な太い黒のピクセル境界線と角ばった角しかなく、ブラウザがOSのダイアログ/ウィンドウ配置慣例に従って、ネイティブのmacOS、iOS、Windowsのダイアログのように見せてくれることを期待していたのとは違っていた
開いた要素は別の最上位レイヤーにあるのだから、ブラウザのビューポート外にも出せることを期待したが、その期待も外れた。ユーザーの立場では、文書内容を完全に覆う動かせないポップアップやモーダルダイアログを望んでいないからだ
ブラウザベンダーは
alert()、prompt()、confirm()がJavaScript/メインスレッドをブロックするので使ってほしくないようだが、それと同じくらい簡単で効果的で、UIデザイン能力を要求しない代替APIは出せていない。あらゆる場面で<dialog>のほうが優れていると説得しようとするより、Promiseベースのノンブロッキングな alert/prompt/confirm APIを提供してほしい新機能が昔の
alert()系のように動かなくなったのは、単なる残念な偶然ではなく利点でもあるただし
<dialog>のデフォルトスタイルにはもう少し配慮が必要で、DOM外のシステムダイアログのように100%見えて動作しなくても、ブラウザのデフォルトスタイルと調和する標準スタイルがあるだけでもずっとよかったはずだPWAのように、通常のページより権限の強いWebアプリであれば、ウィンドウのスタイリングやシステムと相互作用する別機能のような、この方向性はより受け入れられるかもしれない
<dialog>をネイティブのブラウザウィンドウのように見せると、line of deathを曖昧にしかねない悪意あるWebサイトがページ中央に「ブラウザを更新」ポップアップを表示し、それらしいChromeダウンロードページへリダイレクトして、改ざんされた実行ファイルをダウンロードさせることが容易になる
https://textslashplain.com/2017/01/14/the-line-of-death/
ユーザーは複数のタブを開いており、別のタブにダイアログを完了するのに必要な情報があるかもしれない
実際のダイアログにどれだけの視覚的制御権を与えるかについても、非常に慎重であるべきだ。特にOSのように見せると、今でも偽のブラウザウイルス警告にだまされる人が多いのに、本物らしく見えて必ず操作しなければならないダイアログは、攻撃の成功率を大きく高めうる
働いていた会社のほとんどにはある程度一貫したデザイン言語があり、純粋なHTML/CSSを好む人が何と言おうと、組み込みブラウザコンポーネントをそのまま使うとたいてい素人っぽくちぐはぐに見えた
この問題が解決されない限り、広く使われるのは難しい
alert()、prompt()、confirm()の代替を、await Prompts.alert("This is an alert message!");、await Prompts.confirm(...)、await Prompts.prompt(...)のように動作させるアイデアを試しているデモ: https://tools.simonwillison.net/prompts-js
コードはo1が作成: https://chatgpt.com/share/67539c28-4df0-8006-b021-4f468e011f...
「The HTML dialog element API is a mess」という記事を書いた: https://lapcatsoftware.com/articles/2024/2/1.html
それでも価値は標準であることにある。Webはどこでも動くという点で優れており、どうしても雑多にならざるを得ない
この文脈では、
<dialog>は特に社内管理ツールでは成功だと思う。最新のフロントエンド流行を気にせず、画面スペースを節約しながらメインビューの上にコンテンツをモーダルオーバーレイとして表示したいだけだからだダイアログを開くにはユーザー入力が必要で、モーダルダイアログはユーザー入力を妨げるので、これが起こるにはダイアログ内の入力が再びそのダイアログを開かなければならない
非同期処理でダイアログを開くなら何が開いていて何が閉じているかを追跡する必要があり、Qtのような環境でも似たようなことは起こる
<dialog>が、複数回開いてもブラウザごとに警告が出ず、カスタマイズ可能なawait confirm/promptの強化版になってくれることを期待していただが実際には、装飾された
divに近いCompression Streams APIは悪くなかったが、とても小さなAPIだった
このパターンを見ると、Googleはユーザー体験と開発者体験の扱いがあまりうまくないように思える
追記として標準の立場を調べたら、両方ともURLPatternをサポートしていた
実装とは別に、正しい方向への一歩だったと思う
現在は
<select>を強化したような提案も進んでいる: https://open-ui.org/components/combobox.explainer/トースト通知向けには、すでにブラウザにPopover APIが入っている: https://mdn.github.io/dom-examples/popover-api/
ツールチップのためのpopover hint提案もある: https://open-ui.org/components/popover-hint.research.explain...
**
<dialog>**要素は気に入っており、特に組み込みで標準化されたアクセシビリティ配慮が良いSafari 15.4未満がサポート対象から外れれば、polyfillなしで使える日を楽しみにしている
ただ最大の不満はJavaScript依存であることだ。サイト訪問者のほぼ全員がJavaScriptを有効にして来るだろうが、あえてそれを不要にできることに満足感を覚えることもある
なぜCSSや対象ボタンでダイアログの開いた状態を制御できないのかわからず、もし自分が間違っているなら知りたい
popover=""属性を付けたカスタム要素を使っているボタンで簡単に対象にでき、クライアント側JavaScriptが不要で、外側をクリックすれば閉じることもできる。
<dialog>には標準ではない動作だアクセシビリティについては自信がないが、隠しラベル/チェックボックス/フォーム要素で作っていた昔のやり方よりは悪くなさそうで、ずっと単純でハックっぽさも少ない
https://css-tricks.com/invoker-commands-additional-ways-to-w...
HTMLが、1つのファイル内に複数のページを定義して一度に1つずつ表示できる**
<page>のようなタグ**という概念をサポートするとよいと思うダイアログのようには見えず、各PAGEが選択状態に応じて同じページ内のヘッダー、サイドバー、フッターのような共通セクションを引き上げられるとよい
今でも
divを隠したり表示したりする方法で実現できるが、特定のHTMLタグでこうしたアプローチを支援してくれると助かる今日使っていて、回避できない問題があった
ダイアログ内にフォームがあると、入力にフォーカスした状態でEnterを押すか、送信ボタンにフォーカスした状態でSpaceを押したとき、フォーム送信とともにダイアログが閉じる
これをきれいに防ぐ方法が見つからなかった。通常フォームはページを再読み込みするので一般的な問題ではないかもしれないが、ここではhtmxを使っていた
<dialog>内のフォームでiframeを更新するエディタを使っているが、対象のiframeが再読み込みされるだけでダイアログは閉じないEnterを押したときにダイアログが閉じるケースも再現できなかった。
submitと同じはずで、submitでもダイアログは閉じないちなみにボタンがダイアログを閉じられることは昨日知った
preventDefaultとstopPropagationを使えばよいのでは?新しく最適化したビルドに入れるCookie同意マネージャーを探していたが、オープンソースの選択肢が100KBを超えているのが気に入らず、自分で作った: https://github.com/replete/biscuitman
できるだけ小さく書くために**
<dialog>**に依存し、CSSルールをいくつか書くだけでスタイルなしでもネイティブに動作する結局、IE11や本当に古いブラウザバージョン向けまでコンパイルするビルドツールまで書くことになった
<dialog>は概ねうまく動作し、古いブラウザではCSSの小技が少し必要だが扱いにくくはない。Webプラットフォームへの悪くない追加要素だが、この仕事を20年やってきて、数年ごとにカスタムの複数選択コントロールを作り直したいとは思わない。ネイティブコントロールが良いほとんどのサンプルの一般的な閉じる動作が Android版Firefox では動作しない
autofocus属性が動作しなかった結局、
show()を呼ぶたびに$('#thatModal *[autofocus]').focus()のような1行を追加したMDNによれば、本来はデフォルトで意図どおりに動作するはず
JavaScriptで追加したリスナーだけが正しく動作しているようだ