コンテンツにスキップ

ui:modal

汎用的なModal Dialogコンポーネントです。

  • Web Component (<ui-modal>) として提供しています。
  • WAI-ARIA Authoring Practices の Dialog (Modal) Pattern に準拠しています。
Source Code
<button type="button" data-ui-modal-open="basic-modal">基本モーダルを開く</button>
<ui-modal class="ui:modal" id="basic-modal" aria-labelledby="basic-modal-title">
<header class="ui:modal__header">
<h2 id="basic-modal-title">お知らせ</h2>
<button type="button" data-ui-modal-close aria-label="閉じる">×</button>
</header>
<div class="ui:modal__body">
<p>本日 22:00 〜 23:00 にメンテナンスを実施します。</p>
</div>
<footer class="ui:modal__footer">
<button type="button" data-ui-modal-close>了解</button>
</footer>
</ui-modal>

<form method="dialog"> を使うと、submit ボタンの value<dialog>.returnValue に入り、ui-modal:closedetail.returnValue で取得できます。

Source Code
<button type="button" data-ui-modal-open="confirm-modal">削除する</button>
<ui-modal class="ui:modal" id="confirm-modal" aria-labelledby="confirm-modal-title">
<form method="dialog">
<header class="ui:modal__header">
<h2 id="confirm-modal-title">本当に削除しますか?</h2>
</header>
<div class="ui:modal__body">
<p>この操作は取り消せません。</p>
</div>
<footer class="ui:modal__footer">
<button type="submit" value="cancel">キャンセル</button>
<button type="submit" value="confirm">削除する</button>
</footer>
</form>
</ui-modal>
属性要素効果
data-ui-modal-open任意対象 modal の idクリックで対応する <ui-modal> を開く
data-ui-modal-close任意-<ui-modal> 内のクリックでそのモーダルを閉じる
キー動作
Escape Escape モーダルを閉じる
Tab Tab モーダル内のフォーカス移動(外には出ない)
経路仕組み
Escape Escape キーネイティブ <dialog>
data-ui-modal-close 付き要素のクリック<ui-modal> 内 delegate
モーダル外側(backdrop)のクリックdialog の padding:0 + e.target === dialog 判定で light-dismiss
<form method="dialog"> の submitネイティブ
JS から modalElement.close(returnValue?)API
要件対応
role="dialog" / aria-modal<dialog>.showModal() が ネイティブ適用
ラベル著者が <ui-modal aria-labelledby="..."> 等を指定 (必須推奨)
フォーカストラップネイティブ
開く前の要素へのフォーカス復帰ネイティブ
Escape Escape 閉じネイティブ
背景スクロール抑止<html>overflow:hidden を退避 / 復元

<ui-modal> は Light DOM の Web Component ですが、ユーザーアクションによって表示される性質上、JS upgrade 前は 完全に非表示 にしています。

<!-- どこかのトリガー -->
<button type="button" data-ui-modal-open="my-modal">開く</button>
<!-- モーダル本体 (どこに書いても OK) -->
<ui-modal class="ui:modal" id="my-modal" aria-labelledby="my-modal-title">
<header class="ui:modal__header">
<h2 id="my-modal-title">タイトル</h2>
<button type="button" data-ui-modal-close aria-label="閉じる">×</button>
</header>
<div class="ui:modal__body">
<p>本文...</p>
</div>
<footer class="ui:modal__footer">
<button type="button" data-ui-modal-close>キャンセル</button>
<button type="submit">送信</button>
</footer>
</ui-modal>
  • ルート: <ui-modal class="ui:modal" id="..."> カスタム要素 (id 必須、トリガーが参照する)
  • ラベル: aria-labelledby または aria-label<ui-modal> 付与すれば内部の <dialog> に転写されます
  • 補足説明 (任意): aria-describedby<ui-modal> に付与すれば内部の <dialog> に転写されます
  • 推奨子要素: .ui:modal__header / .ui:modal__body / .ui:modal__footer (任意、共通スタイル用)
<ui-modal class="ui:modal" id="my-modal" aria-labelledby="my-modal-title" data-ui-modal-ready>
<!-- JS が動的に挿入。著者が書いた children は全てこの中に移動される -->
<dialog aria-labelledby="my-modal-title">
<header class="ui:modal__header">...</header>
<div class="ui:modal__body">...</div>
<footer class="ui:modal__footer">...</footer>
</dialog>
</ui-modal>
import type { UiModalElement } from "@packages/ui/modal";
const $modal = document.querySelector<UiModalElement>("#my-modal");
$modal?.open(); // 開く (programmatic、トリガーなし)
$modal?.close(); // 閉じる
$modal?.close("done"); // returnValue 付きで閉じる
console.log($modal?.isOpen); // 開閉状態
イベント発火元cancelablebubblesdetail
ui-modal:beforeopen<ui-modal>{ $trigger: HTMLElement | null }
ui-modal:open<ui-modal>{ $trigger: HTMLElement | null }
ui-modal:close<ui-modal>{ returnValue: string }
import type { ModalOpenDetail, ModalCloseDetail } from "@packages/ui/modal";
// open をキャンセルする例 (例: 未保存の編集を確認)
document.addEventListener("ui-modal:beforeopen", (e) => {
if (someValidation()) {
e.preventDefault();
}
});
document.addEventListener("ui-modal:close", (e) => {
console.log("closed with:", e.detail.returnValue);
});

$triggerdata-ui-modal-open 付きの要素 (クリックで開かれた場合)。element.open() を直接呼んだ場合は null