總覽
請使用原生 <dialog> 元素,建立強制回應和非強制回應對話方塊。它提供無障礙語意、焦點處理、頂層算繪和 Esc 處理,而自訂疊加層必須手動重建。
選擇疊加類型
選取第一個符合的規則。如果不確定,請使用規則 1。
規則 1:重疊廣告干擾使用者並遮蓋網頁 (預設)
適用於確認、表單、登入、破壞性動作,以及任何未另行說明的「互動視窗」、「對話方塊」、「燈箱」或「彈出式視窗」要求。
使用 <dialog> command="show-modal" 以宣告方式開啟為強制回應視窗 (請參閱宣告式對話方塊和彈出式視窗控制項),或使用 showModal() 從指令碼開啟。
<button type="button" command="show-modal" commandfor="confirm">Delete</button>
<!-- MANDATORY: no role="dialog" or aria-modal; the native modal already provides them -->
<dialog id="confirm" aria-labelledby="confirm-title">
<h2 id="confirm-title">Delete this item?</h2>
<form method="dialog">
<button value="cancel" autofocus>Cancel</button>
<button value="delete">Delete</button>
</form>
</dialog>
瀏覽器提供:惰性頁面、焦點限制、頂層、::backdrop、Esc 和返回手勢關閉,以及焦點返回叫用器。
- 請勿新增
role="dialog"、aria-modal、焦點陷阱、inert切換或Esc處理常式。 - 請勿使用
show()或open屬性開啟。 - 務必使用下列 CSS 停止捲動背景頁面。詳情請參閱「防止強制回應對話方塊下方的頁面捲動」。
- 請勿在
html或body上使用overflow: hidden或position: fixed鎖定捲動功能。
/* MANDATORY (both rules): without them, scrolling over the backdrop or inside the dialog scrolls the page behind it */
dialog:modal {
overscroll-behavior: contain; /* stops scroll gestures in the dialog chaining to the page */
}
dialog::backdrop {
overflow: hidden; /* ::backdrop is not a scroll container by default, so it would ignore overscroll-behavior */
overscroll-behavior: contain;
}
不適用於選單或工具提示:請參閱規則 3。
規則 2:面板開啟時,使用者仍可在頁面中繼續操作
用於尋找列、檢查器、調色盤和撰寫窗格,這些在語意上仍屬於對話方塊。
使用 <dialog popover> 建立頂層面板,並設定彈出式視窗關閉行為 (auto 會輕觸關閉,manual 則不會)。只有在面板不應位於頂層時,才使用 dialog.show()。
<button type="button" popovertarget="find">Find</button>
<dialog id="find" popover="manual" aria-labelledby="find-title">
<h2 id="find-title">Find</h2>
<input type="search" aria-label="Search">
<button type="button" popovertarget="find" popovertargetaction="hide">Close</button>
</dialog>
頁面會保持互動狀態,不會變成無效。
- 務必加入清楚可見的關閉按鈕。
- 請勿使用
showModal(),否則會封鎖網頁。
不適用於封鎖確認:請參閱規則 1。
規則 3:疊加層不是對話方塊
適用於選單、下拉式選單、工具提示、懸浮資訊卡和浮動式訊息。
請使用含有 popover 屬性的語意元素,而非 <dialog>。
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
選擇關閉方式
預設值取決於對話方塊的開啟方式。如果 closedby 遺失或無效,showModal() 的行為會與 closerequest 相同,而任何其他開啟方式的行為會與 none 相同。
- 僅限 Esc 鍵和返回手勢 (互動視窗預設值):不設定任何值,或設定
closedby="closerequest"。 - 點選對話方塊外側也可關閉:使用
closedby="any"。 - 只有開發人員可以關閉 (例如含有未儲存變更的表單):使用
closedby="none"並新增確認路徑。請參閱下方的「結語」。
規範
無障礙元素名稱和初始焦點
- 務必使用
aria-labelledby指向標題,為每個對話方塊命名,或在沒有可見標題時使用aria-label。 - 務必將
autofocus放在主要控制項或第一個欄位。 - 務必在破壞性確認中,將
autofocus放在安全動作 (例如「取消」或「保留」) 上。 - 請勿編寫程式碼,在關閉時傳回焦點。瀏覽器會將其傳回給叫用者。
開場白
- 建議使用
command="show-modal"和commandfor在<button>上以宣告方式開啟。請參閱「宣告式對話方塊和彈出式視窗控制項」。 - 務必在從指令碼開啟時使用
dialog.showModal()。
Closing
- 務必使用
<form method="dialog">按鈕或command="close"關閉。 - 務必謹慎選擇
closedby:any(輕觸關閉和Esc)、closerequest(僅限Esc,模態預設值) 或none(僅限開發人員控制的關閉)。 - 建議使用
command="request-close"或requestClose()建立「取消」按鈕,以遵循未儲存變更邏輯。系統會先觸發cancel,並忽略closedby。 - 務必將
closedby視為漸進增強。Esc和行動裝置返回手勢已可關閉互動視窗,不需使用這個屬性。
連結至關閉
- 務必在
close監聽器中讀取dialog.returnValue,瞭解使用的是哪個按鈕,然後使用dialog.returnValue = ''重設。Esc和其他關閉要求不會設定任何值,且瀏覽器是否會清除先前的值也各有不同,因此如果不重設,舊值可能會外洩。將''視為「未選擇即關閉」。 - 建議使用
cancel攔截Esc和request-close,但請勿在使用者未啟動的情況下,依賴preventDefault()重複按下Esc。 - 務必使用
closedby="none",並針對未儲存的表單加入明確的確認步驟。 - 務必在
cancel遭到禁止時,告知使用者對話方塊保持開啟的原因:顯示可見訊息,並從接收焦點的控制項 (例如「捨棄」) 使用aria-describedby參照該訊息,以便螢幕閱讀器在焦點移動時朗讀該訊息。如果沒有這個屬性,焦點移到新按鈕時,系統只會讀出該按鈕的名稱。 - 請勿讓確認動作移動游標下方的控制項。保留訊息空間 (使用
visibility隱藏,而非display或hidden),並在現有按鈕不會移動的位置 (例如靠右對齊列的第一個按鈕) 新增破壞性按鈕,這樣一來,在同一位置點按第二次就不會捨棄使用者的變更。
非強制回應對話方塊
- 建議使用
<dialog popover>(auto或manual) 做為頂層面板,並在不需要頂層時使用dialog.show()。 - 務必在每個非強制回應對話方塊中,加入可見的關閉按鈕。
背景和造型
- 務必設定
::backdrop的樣式,並使用dialog:modal指定僅限互動視窗的樣式。 - 務必將動畫留給「在頂層之間建立元素動畫」,將拖曳留給「建構可拖曳元素」,並將重新設定父項留給「永久頂層 UI」。
- 請勿無條件在
dialog上設定display。會覆寫關閉狀態。使用dialog[open]設定範圍。
無障礙設定
- 請勿在原生互動視窗中加入
role="dialog"、aria-modal、手動焦點陷阱或inert切換功能。 - 建議使用原生
<button>,並為關閉控制項設定無障礙元素名稱。 - 建議:對話方塊標題一律使用相同的標題層級。
備用策略
closedby (dialog-closedby)
如果沒有 closedby,互動視窗仍會透過 Esc 和返回手勢關閉,只有點選外部的功能會失效,因此這是優雅降級。系統會以相同方式忽略 closedby="none":Esc 會觸發 cancel,然後關閉對話方塊。請將未儲存變更防護機制保留在 cancel 處理常式中,而非僅保留在 closedby="none" 中,這樣一來,即使缺少 closedby,也能受到保護。如果基準目標需要輕觸關閉,請在缺少 closedby 的地方新增:
if (!('closedBy' in HTMLDialogElement.prototype)) {
// Test the pointer position, not event.target: the <dialog> is also the target for its own
// padding and the gaps between its children, so a target check would close on those clicks
const outside = (e, dialog) => {
const r = dialog.getBoundingClientRect();
return e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
};
for (const dialog of document.querySelectorAll('dialog[closedby="any"]')) {
let pressedOutside = false;
dialog.addEventListener('pointerdown', (e) => { pressedOutside = outside(e, dialog); });
// Require the press and the release outside, so a text-selection drag that ends on the backdrop does not dismiss
dialog.addEventListener('pointerup', (e) => { if (pressedOutside && outside(e, dialog)) dialog.close(); });
}
}
overscroll-behavior
將捲動限制視為漸進增強。請勿偵測功能:即使不可捲動的容器忽略該屬性,@supports 也會回報支援該屬性。如果未強制執行,對話方塊仍會在內容溢位時捲動,且 showModal() 仍會使頁面處於閒置狀態。請勿在 html 或 body 上改用 overflow: hidden 或 position: fixed,否則會移除根捲軸並移動版面配置。
叫用器指令 (command/commandfor)
如果 Baseline 目標不包含 Invoker 指令,請保留相同的標記,並視情況載入 polyfill (與漸進增強功能相同的體驗)。偵測介面,絕不無條件載入:
// MANDATORY: feature detect on HTMLButtonElement.prototype and load only when native support is missing
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('invokers-polyfill'); // npm install invokers-polyfill
}
只有規則 2 和 3 使用彈出式視窗。
Popover 的備用方案和瀏覽器支援
Popover API 主要是漸進增強,但其定義行為 (頂層升級、輕觸關閉和 popovertarget 叫用) 沒有僅限 CSS 的對等項目。舊版瀏覽器需要 polyfill,如果不想提供,則需要手動備用方案。
Polyfill:如要在舊版瀏覽器中支援 popover 屬性,請視情況載入 @oddbird/popover-polyfill。必要:檢查 HTMLElement.prototype 上的 popover 屬性,偵測功能,並僅在缺少原生支援時載入 polyfill,請勿無條件載入。
使用套件組合器或匯入對應:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
如果沒有繫結器,請從 <script type="module"> 內的 CDN 匯入:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
樣式注意事項:Polyfill 無法定義實際的 :popover-open 虛擬類別,因此會改為套用 .\:popover-open 類別。必要:請使用 :is() 或 :where() 結合兩者,否則不支援 :popover-open 的瀏覽器會捨棄整個規則:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
或者,如要使用不含 polyfill 的舊版備用方案,請使用 position: fixed,並透過 getBoundingClientRect() 手動計算座標,或在用途允許的情況下,使用 inset: auto 預設定位。