Dialog
ネイティブの dialog 要素を使ったモーダル UI。トリガーや ARIA 属性の関連付け、背景クリックで閉じる操作に対応します。
<pe-dialog>
<button type="button" class="Trigger" data-dialog-trigger>Open dialog</button>
<dialog class="Dialog">
<header class="Header">
<h2 class="Title" data-dialog-title>Example dialog</h2>
<button type="button" class="Close" data-dialog-close>Close</button>
</header>
<p class="Description" data-dialog-description>
A native modal dialog enhanced with trigger wiring and ARIA sync.
</p>
</dialog>
</pe-dialog>.Trigger {
min-height: 2rem;
padding: 0 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
@media (hover: hover) {
&:hover {
background: oklch(92% 0 0);
@media (prefers-color-scheme: dark) {
background: oklch(26.9% 0 0);
}
}
}
}
pe-dialog {
display: contents;
}
.Dialog {
width: min(20rem, calc(100vw - 2rem));
margin: 0;
padding: 1rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: oklch(97% 0 0);
color: oklch(14.5% 0 0);
@media (prefers-color-scheme: dark) {
border-color: white;
background: oklch(20% 0 0);
color: white;
}
&::backdrop {
background: oklch(0% 0 0 / 45%);
@media (prefers-color-scheme: dark) {
background: oklch(0% 0 0 / 65%);
}
}
}
.Dialog .Header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.75rem;
}
.Dialog .Title {
margin: 0;
font-size: 1rem;
font-weight: 500;
line-height: 1.25rem;
}
.Dialog .Description {
margin: 0;
line-height: 1.5;
}
.Dialog .Close {
min-height: 2rem;
padding: 0 0.5rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
@media (hover: hover) {
&:hover {
background: oklch(92% 0 0);
@media (prefers-color-scheme: dark) {
background: oklch(26.9% 0 0);
}
}
}
}
.Dialog .Actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 1rem;
}
.Dialog .Actions button {
min-height: 2rem;
padding: 0 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
}
.Dialog .ButtonDanger {
border-color: oklch(14.5% 0 0);
background: oklch(14.5% 0 0);
color: white;
@media (prefers-color-scheme: dark) {
border-color: white;
background: white;
color: oklch(14.5% 0 0);
}
}
Dialog はモーダルタスクや確認に使います。背景操作で閉じる場合だけ data-dialog-dismiss を指定し、単純な確認・キャンセルにはネイティブの form method="dialog" を使います。
| パーツ | セレクター | 役割 |
|---|---|---|
| ホスト | <pe-dialog> |
1 つの <dialog> の状態とライフサイクルを管理します |
| Dialog | <dialog> |
モーダルとして表示するネイティブ要素 |
| トリガー | [data-dialog-trigger] |
ダイアログを開きます。値が空なら、同じホスト内のダイアログが対象です |
| タイトル | [data-dialog-title] |
明示的なラベルがないとき aria-labelledby を設定します |
| 説明 | [data-dialog-description] |
aria-describedby を設定します |
| 閉じる | [data-dialog-close] |
クリックでダイアログを閉じます |
| 背景クリック | <dialog> 上の data-dialog-dismiss |
背景を押したときにダイアログを閉じます |
アラートダイアログ
Section titled “アラートダイアログ”すぐに応答が必要な短いメッセージには role="alertdialog" を指定します。
<button type="button" class="Trigger" data-dialog-trigger="delete-dialog">
Delete item
</button>
<pe-dialog id="delete-dialog">
<dialog class="Dialog" role="alertdialog" aria-modal="true">
<h2 class="Title" data-dialog-title>Delete this item?</h2>
<p class="Description" data-dialog-description>
This action cannot be undone.
</p>
<form class="Actions" method="dialog">
<button value="cancel" autofocus>Cancel</button>
<button class="ButtonDanger" value="confirm">Delete</button>
</form>
</dialog>
</pe-dialog>
<p id="delete-result" class="Status" role="status"></p>.Row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.Status {
margin: 0;
}
.Trigger {
min-height: 2rem;
padding: 0 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
@media (hover: hover) {
&:hover {
background: oklch(92% 0 0);
@media (prefers-color-scheme: dark) {
background: oklch(26.9% 0 0);
}
}
}
}
pe-dialog {
display: contents;
}
.Dialog {
width: min(20rem, calc(100vw - 2rem));
margin: 0;
padding: 1rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: oklch(97% 0 0);
color: oklch(14.5% 0 0);
@media (prefers-color-scheme: dark) {
border-color: white;
background: oklch(20% 0 0);
color: white;
}
&::backdrop {
background: oklch(0% 0 0 / 45%);
@media (prefers-color-scheme: dark) {
background: oklch(0% 0 0 / 65%);
}
}
}
.Dialog .Header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.75rem;
}
.Dialog .Title {
margin: 0;
font-size: 1rem;
font-weight: 500;
line-height: 1.25rem;
}
.Dialog .Description {
margin: 0;
line-height: 1.5;
}
.Dialog .Close {
min-height: 2rem;
padding: 0 0.5rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
@media (hover: hover) {
&:hover {
background: oklch(92% 0 0);
@media (prefers-color-scheme: dark) {
background: oklch(26.9% 0 0);
}
}
}
}
.Dialog .Actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 1rem;
}
.Dialog .Actions button {
min-height: 2rem;
padding: 0 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
}
.Dialog .ButtonDanger {
border-color: oklch(14.5% 0 0);
background: oklch(14.5% 0 0);
color: white;
@media (prefers-color-scheme: dark) {
border-color: white;
background: white;
color: oklch(14.5% 0 0);
}
}
- 名前と説明を用意する。
data-dialog-dismissは指定しない。- 最も安全なボタンに
autofocusを付ける。 - 明示的なフォーム確認後だけ処理を実行する。
const host = document.querySelector("#delete-dialog");
host.addEventListener("pe-dialog:close", (event) => { const { dialog, reason } = event.detail;
if (reason === "form" && dialog.returnValue === "confirm") { deleteItem(); }});API リファレンス
Section titled “API リファレンス”ホストメソッド
Section titled “ホストメソッド”| 名前 | 型 | 説明 |
|---|---|---|
isOpen | boolean (readonly) | ダイアログが現在開いているかどうかを読み取ります。 |
open(trigger?) | void | showModal() でダイアログを開きます。JavaScript から開く場合は、フォーカスを戻す要素として、操作元のトリガーを渡せます。 |
close(returnValue?) | void | ダイアログを閉じます。returnValue を指定すると、HTMLDialogElement.close() へ渡されます。 |
toggle(trigger?) | void | 閉じていれば開き、開いていれば閉じます。 |
data 属性
Section titled “data 属性”| 名前 | 説明 |
|---|---|
data-dialog-trigger | ダイアログを開く要素に指定します。値が空なら同じホスト内、ホストの id を指定した場合は対応する外部ダイアログが対象です。 |
data-dialog-close | クリックまたはキーボードで操作すると、ダイアログを閉じます。 |
data-dialog-title | aria-labelledby で参照するタイトル要素を示します。 |
data-dialog-description | aria-describedby で参照する説明要素を示します。 |
data-dialog-dismiss | <dialog> に指定すると、背景を押したときにダイアログを閉じます。 |
data-state | ホスト、ダイアログ、トリガーに "open" または "closed" を設定します。トリガーが複数ある場合、実際に開いたトリガーだけが "open" になります。 |
| 名前 | 説明 |
|---|---|
pe-dialog:open | ダイアログが開いた後に発火します。detail には dialog、オプションの trigger、reason が含まれます。 |
pe-dialog:close | ダイアログが閉じた後に発火します。detail には dialog、オプションの trigger、reason が含まれます。 |
pe-dialog:cancel | ネイティブの cancel イベントを処理した後に発火します。detail には dialog、任意の trigger、reason、ネイティブイベントがキャンセルされたかどうかが含まれます。 |
detail.reason には、閉じた理由として trigger、close-control、form、cancel、dismiss、programmatic、native のいずれかが入ります。
ネイティブの cancel イベントがキャンセルされた場合、detail.defaultPrevented が true の pe-dialog:cancel が発火します。ダイアログは開いたままとなり、pe-dialog:close は発火しません。
ネストされたダイアログ
Section titled “ネストされたダイアログ”<pe-dialog> を別のダイアログ内に置くと、ダイアログを入れ子にできます。フォーカスを戻す処理と閉じる操作は、最前面にあるダイアログに対して行われます。
<button type="button" class="Trigger" data-dialog-trigger="parent-dialog">
Open parent dialog
</button>
<pe-dialog id="parent-dialog">
<dialog class="Dialog" data-dialog-dismiss>
<header class="Header">
<h2 class="Title" data-dialog-title>Parent dialog</h2>
<button type="button" class="Close" data-dialog-close>Close parent</button>
</header>
<p class="Description" data-dialog-description>
Open a child dialog without closing this parent dialog.
</p>
<button type="button" class="Trigger" data-dialog-trigger="child-dialog">
Open child dialog
</button>
<pe-dialog id="child-dialog">
<dialog class="Dialog">
<header class="Header">
<h2 class="Title" data-dialog-title>Child dialog</h2>
<button type="button" class="Close" data-dialog-close>Close child</button>
</header>
<p class="Description" data-dialog-description>
Closing this dialog restores focus to its trigger in the parent dialog.
</p>
</dialog>
</pe-dialog>
</dialog>
</pe-dialog>.Trigger {
min-height: 2rem;
padding: 0 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
@media (hover: hover) {
&:hover {
background: oklch(92% 0 0);
@media (prefers-color-scheme: dark) {
background: oklch(26.9% 0 0);
}
}
}
}
pe-dialog {
display: contents;
}
.Dialog {
width: min(20rem, calc(100vw - 2rem));
margin: 0;
padding: 1rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: oklch(97% 0 0);
color: oklch(14.5% 0 0);
@media (prefers-color-scheme: dark) {
border-color: white;
background: oklch(20% 0 0);
color: white;
}
&::backdrop {
background: oklch(0% 0 0 / 45%);
@media (prefers-color-scheme: dark) {
background: oklch(0% 0 0 / 65%);
}
}
}
.Dialog .Header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.75rem;
}
.Dialog .Title {
margin: 0;
font-size: 1rem;
font-weight: 500;
line-height: 1.25rem;
}
.Dialog .Description {
margin: 0;
line-height: 1.5;
}
.Dialog .Close {
min-height: 2rem;
padding: 0 0.5rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
@media (hover: hover) {
&:hover {
background: oklch(92% 0 0);
@media (prefers-color-scheme: dark) {
background: oklch(26.9% 0 0);
}
}
}
}
.Dialog .Actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 1rem;
}
.Dialog .Actions button {
min-height: 2rem;
padding: 0 0.75rem;
border: 1px solid oklch(14.5% 0 0);
border-radius: 0;
background: transparent;
color: inherit;
cursor: pointer;
@media (prefers-color-scheme: dark) {
border-color: white;
}
}
.Dialog .ButtonDanger {
border-color: oklch(14.5% 0 0);
background: oklch(14.5% 0 0);
color: white;
@media (prefers-color-scheme: dark) {
border-color: white;
background: white;
color: oklch(14.5% 0 0);
}
}
ネストされたダイアログでは次のスタイリングフックを利用できます。
| 名前 | 説明 |
|---|---|
data-nested |
ダイアログが別のダイアログ内にネストされているときに存在します。 |
data-nested-dialog-open |
このダイアログ内に、開いている子ダイアログがある場合に付きます。 |
--nested-dialogs |
開いている間のネスト深度(ルートダイアログは 0、子は 1、以下同様)。 |