對話方塊 (模態)
一款全方位管理、無呈現對話方塊元件,內建無障礙與鍵盤功能,適用於為您下一個應用程式打造全客製模態和對話方塊視窗。
開始前,請透過 npm 安裝 Headless UI。
請注意,此程式庫僅支援 Vue 3。
npm install @headlessui/vue
對話方塊採用 Dialog
、DialogPanel
、DialogTitle
和 DialogDescription
元件建立。
當對話方塊的 open
道具為 true
時,對話方塊內容將會呈現。焦點會移至對話方塊內部,並當使用者在可聚焦元素中切換時,焦點將被鎖定在對話方塊內。捲軸將會被鎖定,您應用程式介面的其餘部分將會隱藏在螢幕閱讀程式中,而點擊 DialogPanel
外部或按下 Escape 鍵時,將會觸發 close
事件,然後關閉對話方塊。
<template> <Dialog :open="isOpen" @close="setIsOpen"> <DialogPanel> <DialogTitle>Deactivate account</DialogTitle> <DialogDescription> This will permanently deactivate your account </DialogDescription> <p> Are you sure you want to deactivate your account? All of your data will be permanently removed. This action cannot be undone. </p> <button @click="setIsOpen(false)">Deactivate</button> <button @click="setIsOpen(false)">Cancel</button> </DialogPanel> </Dialog> </template> <script setup> import { ref } from 'vue' import { Dialog, DialogPanel, DialogTitle, DialogDescription, } from '@headlessui/vue' const isOpen = ref(true) function setIsOpen(value) { isOpen.value = value } </script>
對話方塊如果有標題和說明,請使用 DialogTitle
和 DialogDescription
元件來提供最佳的無障礙體驗。這會將您的標題和說明透過 aria-labelledby
和 aria-describedby
屬性連結至對話方塊根元件,確保當您的對話方塊開啟時,螢幕閱讀程式使用者會聽到這些內容。
對話方塊不會自動管理它們的開啟/關閉狀態。如果要顯示和隱藏對話方塊,請將參考傳遞至 open
道具。當 open
為 true
時,對話方塊會呈現,而當 open
為 false
時,對話方塊會解除掛載。
當開啟的對話方塊關閉時,會觸發 close
事件,此事件會在使用者按一下 DialogPanel
外部或按下 Escape 鍵時發生。你可以使用此事件將 open
設回 false,並關閉對話方塊。
<template> <!-- Pass the `isOpen` ref to the `open` prop, and use the `close` event to set the ref back to `false` when the user clicks outside of the dialog or presses the escape key. -->
<Dialog :open="isOpen" @close="setIsOpen"><DialogPanel> <DialogTitle>Deactivate account</DialogTitle> <DialogDescription> This will permanently deactivate your account </DialogDescription> <p> Are you sure you want to deactivate your account? All of your data will be permanently removed. This action cannot be undone. </p> <!-- You can render additional buttons to dismiss your dialog by setting your `isOpen` state to `false`. --> <button @click="setIsOpen(false)">Cancel</button> <button @click="handleDeactivate">Deactivate</button></DialogPanel></Dialog> </template> <script setup> import { ref } from 'vue' import { Dialog, DialogPanel, DialogTitle, DialogDescription, } from '@headlessui/vue' // The open/closed state lives outside of the Dialog and // is managed by you. const isOpen = ref(true) function setIsOpen(value) {isOpen.value = value} function handleDeactivate() { // ... } </script>
使用 class
或 style
屬性設定 Dialog
和 DialogPanel
元件的樣式,就像你對其他元件所做的那樣。如果需要達成特定的設計,你也可以引入其他元件。
<template> <Dialog :open="isOpen" @close="setIsOpen" class="relative z-50"> <div class="fixed inset-0 flex w-screen items-center justify-center p-4"> <DialogPanel class="w-full max-w-sm rounded bg-white"> <DialogTitle>Complete your order</DialogTitle> <!-- ... --> </DialogPanel> </div> </Dialog> </template> <script setup> import { ref } from 'vue' import { DialogPanel, DialogTitle, DialogDescription } from '@headlessui/vue' const isOpen = ref(true) function setIsOpen(value) { isOpen.value = value } </script>
按一下 DialogPanel
元件外部會關閉對話方塊,因此在決定哪個元件應套用給定樣式時,請記住這一點。
如果你想在 DialogPanel
後面加入覆蓋層或布景,以將注意力集中在面板本身,建議使用專門的元件作為布景,並將其設定為面板容器的同層元件
<template> <Dialog :open="isOpen" @close="setIsOpen" class="relative z-50"> <!-- The backdrop, rendered as a fixed sibling to the panel container -->
<div class="fixed inset-0 bg-black/30" aria-hidden="true" /><!-- Full-screen container to center the panel --> <div class="fixed inset-0 flex w-screen items-center justify-center p-4"> <!-- The actual dialog panel --> <DialogPanel class="w-full max-w-sm rounded bg-white"> <DialogTitle>Complete your order</DialogTitle> <!-- ... --> </DialogPanel> </div> </Dialog> </template> <script setup> import { ref } from 'vue' import { Dialog, DialogTitle, DialogDescription } from '@headlessui/vue' const isOpen = ref(true) function setIsOpen(value) { isOpen.value = value } </script>
這讓你可以在自己的動畫中單獨讓布景和面板產生 變換,並將其呈現為同層元件,可確保布景不會干擾你向下捲動長對話方塊的能力。
讓對話方塊可捲動完全在 CSS 中處理,具體的實作取決於你嘗試達成的設計。
以下是一個範例,其中整個面板容器可捲動,而面板本身在你捲動時移動
<template> <Dialog :open="isOpen" @close="setIsOpen" class="relative z-50"> <!-- The backdrop, rendered as a fixed sibling to the panel container --> <div class="fixed inset-0 bg-black/30" aria-hidden="true" /> <!-- Full-screen scrollable container -->
<div class="fixed inset-0 w-screen overflow-y-auto"><!-- Container to center the panel --><div class="flex min-h-full items-center justify-center p-4"><!-- The actual dialog panel --> <DialogPanel class="w-full max-w-sm rounded bg-white"> <DialogTitle>Complete your order</DialogTitle> <!-- ... --> </DialogPanel> </div> </div> </Dialog> </template> <script setup> import { ref } from 'vue' import { Dialog, DialogTitle, DialogDescription } from '@headlessui/vue' const isOpen = ref(true) function setIsOpen(value) { isOpen.value = value } </script>
在使用布景建立可捲動對話方塊時,請確保布景呈現在可捲動容器的 後方,否則當滑鼠游標移到布景時,滾輪將無法作用,而且布景可能會遮蔽滾動條,讓使用者無法用滑鼠按一下它。
基於無障礙考量,你的對話方塊應包含至少一個可獲焦元件。預設情況下,Dialog
元件會在呈現後將焦點集中在第一個可獲焦元件(依 DOM 順序),而按下 Tab 鍵會在內容中的所有其他可獲焦元件之間循環。
對話方塊只要渲染,焦點就會侷限在其中,因此以 Tab 按鍵瀏覽到最後會從頭開始循環。對話框以外的所有其他應用程式元素將標記為不作用,因此無法獲得焦點。
如果您希望在對話框首次渲染時讓最初獲得焦點的不是第一個可獲得焦點的項目,可以使用 initialFocus
參照
<template>
<Dialog :initialFocus="completeButtonRef" :open="isOpen" @close="setIsOpen"><DialogPanel> <DialogTitle>Complete your order</DialogTitle> <p>Your order is all ready!</p> <button @click="setIsOpen(false)">Deactivate</button> <!-- Use `initialFocus` to force initial focus to a specific ref. --><button ref="completeButtonRef" @click="completeOrder">Complete order </button> </DialogPanel> </Dialog> </template> <script setup> import { ref } from 'vue' import { Dialog, DialogPanel, DialogTitle, DialogDescription, } from '@headlessui/vue'const completeButtonRef = ref(null)const isOpen = ref(true) function setIsOpen(value) { isOpen.value = value } function completeOrder() { // ... } </script>
如果你曾經實作過對話框,你大概有接觸過入口網站的概念。入口網站可讓你從 DOM 的一個位置(例如深在應用程式使用者介面上)呼叫元件,但實際上卻渲染在 DOM 的另一個位置。
由於對話框和其背景會佔滿整個頁面,你通常希望將它們作為應用程式根目錄節點的同層節點來渲染。透過這種方式,你可以仰賴 DOM 的自然排序,以確保其內容會渲染在現有的應用程式使用者介面之上。這也有助於輕鬆地將捲軸鎖定套用至應用程式的其他部分,以及確保對話框的內容和背景暢通無阻地接收焦點和點擊事件。
由於這些無障礙考量,Headless UI 的 Dialog
元件實際上會在幕後使用入口網站。透過這種方式,我們可以提供無障礙事件處理和讓應用程式的其他部分不作用等功能。因此,在使用我們的對話框時,無需自己使用入口網站!我們已經處理好了。
若要為對話框的開啟/關閉加入動畫,請將其封裝在 Headless UI 的 TransitionRoot
元件中,並從 Dialog
移除 open
屬性,進而將開啟/關閉狀態傳遞給 TransitionRoot
上的 show
屬性。
<template> <!-- Wrap your dialog in a `TransitionRoot` to add transitions. -->
<TransitionRoot:show="isOpen"as="template"enter="duration-300 ease-out"enter-from="opacity-0"enter-to="opacity-100"leave="duration-200 ease-in"leave-from="opacity-100"leave-to="opacity-0"><Dialog @close="setIsOpen"> <DialogPanel> <DialogTitle>Deactivate account</DialogTitle> <!-- ... --> <button @click="isOpen = false">Close</button> </DialogPanel> </Dialog></TransitionRoot></template> <script setup> import { ref } from 'vue' import {TransitionRoot,Dialog, DialogPanel, DialogTitle, } from '@headlessui/vue' const isOpen = ref(true) function setIsOpen(value) { isOpen.value = value } </script>
若要為背景和面板分別加入動畫,請用 TransitionRoot
封裝你的 Dialog
,並各自用 TransitionChild
封裝你的背景和面板
<template> <!-- Wrap your dialog in a `TransitionRoot`. -->
<TransitionRoot :show="isOpen" as="template"><Dialog @close="setIsOpen"> <!-- Wrap your backdrop in a `TransitionChild`. --><TransitionChildenter="duration-300 ease-out"enter-from="opacity-0"enter-to="opacity-100"leave="duration-200 ease-in"leave-from="opacity-100"leave-to="opacity-0"><div class="fixed inset-0 bg-black/30" /></TransitionChild><!-- Wrap your panel in a `TransitionChild`. --><TransitionChildenter="duration-300 ease-out"enter-from="opacity-0 scale-95"enter-to="opacity-100 scale-100"leave="duration-200 ease-in"leave-from="opacity-100 scale-100"leave-to="opacity-0 scale-95"><DialogPanel> <DialogTitle>Deactivate account</DialogTitle> <!-- ... --> </DialogPanel></TransitionChild></Dialog> </TransitionRoot> </template> <script setup> import { ref } from 'vue' import {TransitionRoot,TransitionChild,Dialog, DialogPanel, DialogTitle, } from '@headlessui/vue' const isOpen = ref(true) function setIsOpen(value) { isOpen.value = value } </script>
如需深入了解 Headless UI 中的轉場,請閱讀專門的 轉場文件。
當對話框的 open
屬性為 true
時,對話框的內容將會呈現,而且焦點會移動到對話框內並被鎖定在對話框內。根據 DOM 順序,第一個可接收焦點的元素將會接收焦點,不過您可以使用 initialFocus
參 chiếu來控制哪個元素接收最初的焦點。在一個開啟的對話框中按下 Tab 鍵,會依序切換所有可接收焦點的元素。
當一個 對話框
呈現時,點擊 對話框面板
外部會關閉 對話框
。
沒有任何預設的滑鼠互動可以開啟 對話框
,不過您通常會連線一個 <button />
元素和一個 click
處理常式,它會切換對話框的 open
屬性為 true
。
命令 | 說明 |
Esc | 關閉所有開啟的對話框 |
Tab | 切換一個開啟的對話框中的內容 |
Shift + Tab | 向後切換一個開啟的對話框中的內容 |
屬性 | 預設值 | 說明 |
open | — | 布林值
|
initialFocus | — | HTMLElement 一個應該首先接收焦點的元素的參照。 |
as | div | 字串 | 元件
|
static | false | 布林值 元素是否應該忽略內部管理的開啟/關閉狀態。 |
unmount | true | 布林值 元素是否應該基於開啟/關閉狀態解除裝載或隱藏。 |
事件 | 說明 |
close | 當 |
Slot 道具 | 說明 |
open |
對話方塊是否開啟。 |
屬性 | 預設值 | 說明 |
as | div | 字串 | 元件
|
Render 道具 | 說明 |
open |
對話方塊是否開啟。 |
屬性 | 預設值 | 說明 |
as | h2 | 字串 | 元件
|
Slot 道具 | 說明 |
open |
對話方塊是否開啟。 |
屬性 | 預設值 | 說明 |
as | p | 字串 | 元件
|
Slot 道具 | 說明 |
open |
對話方塊是否開啟。 |
從 Headless UI v1.6 版起,DialogOverlay
已停用,請參閱 發行說明,了解遷移說明。
屬性 | 預設值 | 說明 |
as | div | 字串 | 元件
|
Slot 道具 | 說明 |
open |
對話方塊是否開啟。 |