Tổng quan
Sử dụng phần tử <dialog> gốc cho hộp thoại phương thức và không theo phương thức. Phần tử này cung cấp ngữ nghĩa hỗ trợ tiếp cận, xử lý tiêu điểm, kết xuất lớp trên cùng và xử lý Esc mà các lớp phủ tuỳ chỉnh phải tạo lại theo cách thủ công.
Chọn loại lớp phủ
Chọn quy tắc đầu tiên phù hợp. Nếu bạn không chắc chắn, hãy sử dụng Quy tắc 1.
Quy tắc 1: Lớp phủ làm gián đoạn người dùng và chặn trang (mặc định)
Sử dụng cho các yêu cầu xác nhận, biểu mẫu, đăng nhập, hành động huỷ bỏ và mọi yêu cầu "phương thức", "hộp thoại", "hộp đèn" hoặc "cửa sổ bật lên" không có quy định khác.
Sử dụng <dialog> được mở dưới dạng phương thức, theo cách khai báo bằng command="show-modal" (xem Hộp thoại khai báo và Kiểm soát cửa sổ bật lên) hoặc từ tập lệnh bằng 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>
Trình duyệt cung cấp: trang không hoạt động, vùng chứa tiêu điểm, lớp trên cùng, ::backdrop, Esc và đóng bằng cử chỉ quay lại, đồng thời trả lại tiêu điểm cho trình gọi.
- KHÔNG thêm
role="dialog",aria-modal, bẫy tiêu điểm, bật/tắtinerthoặc trình xử lýEsc. - KHÔNG mở bằng
show()hoặc thuộc tínhopen. - NÊN dừng thao tác cuộn trang phía sau bằng CSS bên dưới. Hãy xem bài viết Ngăn trang bên dưới hộp thoại phụ cuộn để biết thông tin chi tiết.
- KHÔNG khoá thao tác cuộn bằng
overflow: hiddenhoặcposition: fixedtrênhtmlhoặcbody.
/* 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;
}
Không dùng cho trình đơn hoặc chú thích: xem Quy tắc 3.
Quy tắc 2: Người dùng tiếp tục làm việc trên trang trong khi bảng điều khiển đang mở
Dùng cho thanh tìm kiếm, trình kiểm tra, bảng màu và ngăn soạn thư vẫn là hộp thoại về mặt ngữ nghĩa.
Sử dụng <dialog popover> cho bảng điều khiển lớp trên cùng có tính năng đóng cửa sổ bật lên (auto đóng nhẹ, manual không đóng nhẹ). Chỉ sử dụng dialog.show() khi bảng điều khiển không được nằm ở lớp trên cùng.
<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>
Trang vẫn có thể tương tác và không bị vô hiệu hoá.
- NÊN có nút đóng dễ thấy.
- KHÔNG sử dụng
showModal()vì lệnh này sẽ chặn trang.
Không dùng để chặn thông báo xác nhận: xem Quy tắc 1.
Quy tắc 3: Lớp phủ không phải là hộp thoại
Dùng cho trình đơn, trình đơn thả xuống, chú thích, thẻ di chuột và thông báo ngắn.
Sử dụng phần tử ngữ nghĩa có thuộc tính popover, không phải <dialog>.
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
Chọn cách đóng thông báo
Chế độ mặc định tuỳ thuộc vào cách mở hộp thoại. Nếu closedby không có hoặc không hợp lệ, showModal() sẽ hoạt động như closerequest và mọi cách mở khác sẽ hoạt động như none.
- Chỉ phím Esc và cử chỉ quay lại (mặc định cho phương thức): không đặt gì hoặc đặt
closedby="closerequest". - Cũng nhấp vào bên ngoài để đóng: sử dụng
closedby="any". - Chỉ nhà phát triển mới đóng được (ví dụ: biểu mẫu có các thay đổi chưa lưu): sử dụng
closedby="none"và thêm đường dẫn xác nhận. Xem phần Đóng bên dưới.
Nguyên tắc
Tên thành phần hỗ trợ tiếp cận và tiêu điểm ban đầu
- NÊN đặt tên cho mọi hộp thoại bằng
aria-labelledbytrỏ đến tiêu đề của hộp thoại hoặcaria-labelkhi không có tiêu đề hiển thị. - NÊN đặt
autofocustrên thành phần điều khiển chính hoặc trường đầu tiên. - NÊN đặt
autofocusvào hành động an toàn (chẳng hạn như Huỷ hoặc Giữ lại) trong một thông báo xác nhận có tính huỷ diệt. - KHÔNG viết mã để trả lại tiêu điểm khi đóng. Trình duyệt sẽ trả về giá trị này cho trình gọi.
Từ giữa ra
- NÊN mở theo cách khai báo bằng
command="show-modal"vàcommandfortrên<button>. Xem phần Điều khiển cửa sổ bật lên và hộp thoại khai báo. - NÊN sử dụng
dialog.showModal()khi mở từ tập lệnh.
Closing (Đang đóng)
- NÊN đóng bằng nút
<form method="dialog">hoặccommand="close". - NÊN chọn
closedbymột cách có chủ đích:any(loại bỏ nhẹ vàEsc),closerequest(chỉEsc, mặc định của phương thức) hoặcnone(chỉ đóng do nhà phát triển kiểm soát). - NÊN sử dụng
command="request-close"hoặcrequestClose()cho các nút Huỷ phải tuân theo logic thay đổi chưa lưu. Họ kích hoạtcanceltrước và bỏ quaclosedby. - NÊN coi
closedbylà tính năng cải tiến tăng dần.Escvà cử chỉ quay lại trên thiết bị di động đã đóng các phương thức mà không cần đến nó.
Móc vào để đóng
- NÊN đọc
dialog.returnValuetrong trình nghecloseđể tìm hiểu nút nào đã được sử dụng, sau đó đặt lại nút đó bằngdialog.returnValue = ''.Escvà các yêu cầu đóng khác không đặt giá trị nào, và các trình duyệt khác nhau về việc có xoá giá trị trước đó hay không, vì vậy, nếu không đặt lại, giá trị cũ có thể bị rò rỉ. Coi''là "bị loại bỏ mà không chọn". - NÊN sử dụng
cancelđể chặnEscvàrequest-close, nhưng không dựa vàopreventDefault()để nhấnEscnhiều lần mà không có hoạt động kích hoạt của người dùng. - NÊN sử dụng
closedby="none"với bước xác nhận rõ ràng cho các biểu mẫu chưa được lưu. - NÊN cho người dùng biết lý do hộp thoại vẫn mở khi
cancelbị chặn: hiển thị một thông báo rõ ràng và tham chiếu thông báo đó bằngaria-describedbytừ thành phần điều khiển nhận tiêu điểm (chẳng hạn như Huỷ), để trình đọc màn hình đọc thông báo đó khi tiêu điểm di chuyển. Nếu không có thuộc tính này, thì khi tiêu điểm di chuyển đến một nút mới, trình đọc màn hình chỉ thông báo tên của nút đó. - KHÔNG để nút xác nhận di chuyển xuống dưới con trỏ. Dành không gian cho thông báo (ẩn thông báo bằng
visibility, không phảidisplayhoặchidden) và thêm nút huỷ bỏ ở vị trí mà các nút hiện có không di chuyển (ví dụ: nút đầu tiên trong hàng được căn lề phải), để lượt nhấp thứ hai tại cùng một vị trí không thể loại bỏ các thay đổi của người dùng.
Hộp thoại không phụ thuộc
- NÊN sử dụng
<dialog popover>(autohoặcmanual) cho bảng điều khiển lớp trên cùng vàdialog.show()khi bạn không muốn có lớp trên cùng. - NÊN thêm nút đóng dễ thấy trong mọi hộp thoại không phụ thuộc.
Phông nền và kiểu dáng
- NÊN tạo kiểu cho
::backdropvà sử dụngdialog:modalđể nhắm đến các kiểu chỉ dành cho phương thức. - NÊN để ảnh động cho Tạo ảnh động cho các phần tử đến và đi từ lớp trên cùng, kéo đến Tạo phần tử có thể kéo và thay đổi thành phần mẹ thành Giao diện người dùng lớp trên cùng cố định.
- KHÔNG đặt
displaytrêndialogmột cách vô điều kiện. Trạng thái này sẽ ghi đè trạng thái đóng. Xác định phạm vi bằngdialog[open].
Hỗ trợ tiếp cận
- KHÔNG thêm
role="dialog",aria-modal, bẫy lấy nét thủ công hoặc bật/tắtinertvào một phương thức gốc. - NÊN sử dụng
<button>gốc có tên thành phần hỗ trợ tiếp cận cho các nút điều khiển đóng. - NÊN sử dụng một cấp tiêu đề nhất quán cho tiêu đề hộp thoại.
Chiến lược dự phòng
closedby (dialog-closedby)
Nếu không có closedby, một phương thức vẫn đóng bằng Esc và cử chỉ quay lại; chỉ mất thao tác nhấp bên ngoài, vì vậy, đây là sự xuống cấp nhẹ. closedby="none" cũng bị bỏ qua theo cách tương tự: Esc kích hoạt cancel rồi đóng hộp thoại. Giữ trình bảo vệ các thay đổi chưa lưu trong trình xử lý cancel, chứ không chỉ trong closedby="none", để trình bảo vệ này cũng bảo vệ những nơi thiếu closedby. Nếu mục tiêu Baseline của bạn yêu cầu đóng nhanh, hãy thêm mục này vào nơi thiếu 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
Hãy coi việc chứa cuộn là một tính năng cải tiến tăng dần. Đừng phát hiện tính năng: @supports báo cáo thuộc tính này là được hỗ trợ ngay cả khi các vùng chứa không thể cuộn bỏ qua thuộc tính này. Khi không được thực thi, hộp thoại vẫn chứa thao tác cuộn bất cứ khi nào nội dung của hộp thoại bị tràn và showModal() vẫn khiến trang không hoạt động. Đừng quay lại overflow: hidden hoặc position: fixed trên html hoặc body, vì thao tác này sẽ xoá thanh cuộn gốc và thay đổi bố cục.
Lệnh gọi (command/commandfor)
Nếu mục tiêu Baseline của bạn không bao gồm Lệnh của trình gọi, hãy giữ nguyên mã đánh dấu và tải polyfill theo điều kiện (cùng một trải nghiệm, dưới dạng cải tiến tăng dần). Phát hiện trên giao diện và không bao giờ tải giao diện đó vô điều kiện:
// 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
}
Chỉ Quy tắc 2 và 3 sử dụng Popover.
Phương án dự phòng và hỗ trợ trình duyệt cho Popover
Popover API chủ yếu là cải tiến tăng dần, nhưng các hành vi xác định của API này (quảng bá lớp trên cùng, đóng nhẹ và gọi popovertarget) không có hành vi tương đương chỉ dùng CSS. Các trình duyệt cũ cần có polyfill hoặc phương án dự phòng thủ công nếu bạn không muốn cung cấp polyfill.
Polyfill: Để hỗ trợ thuộc tính popover trong các trình duyệt cũ, hãy tải @oddbird/popover-polyfill theo điều kiện. BẮT BUỘC: Phát hiện tính năng bằng cách kiểm tra thuộc tính popover trên HTMLElement.prototype và chỉ tải polyfill khi thiếu tính năng hỗ trợ gốc — KHÔNG tải polyfill vô điều kiện.
Với trình đóng gói hoặc bản đồ nhập:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Nếu không có trình đóng gói, hãy nhập từ CDN bên trong <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Lưu ý về kiểu: Polyfill không thể xác định lớp giả :popover-open thực, vì vậy, lớp này sẽ áp dụng lớp .\:popover-open. BẮT BUỘC: Kết hợp cả hai bằng :is() hoặc :where(), nếu không, các trình duyệt thiếu :popover-open sẽ loại bỏ toàn bộ quy tắc:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Ngoài ra, đối với phương án dự phòng cũ không có polyfill, hãy sử dụng position: fixed và tính toán toạ độ theo cách thủ công thông qua getBoundingClientRect() hoặc dựa vào vị trí mặc định bằng inset: auto nếu điều đó phù hợp với trường hợp sử dụng.