Modal React yang Mudah Diakses
Dialog modal berasaskan elemen <dialog> asli: fokus masuk dan kembali keluar, Escape menutupnya, dan halaman di belakang tidak aktif (inert).
import { useEffect, useId, useRef } from "react";
export function Modal({ open, onClose, title, children }) {
const dialogRef = useRef(null);
const titleId = useId();
useEffect(() => {
const dialog = dialogRef.current;
if (!open || !dialog) return;
// Remember what had focus so we can give it back
const previouslyFocused = document.activeElement;
dialog.showModal(); // top layer + inert background + Escape support
return () => {
if (dialog.open) dialog.close();
if (previouslyFocused instanceof HTMLElement) previouslyFocused.focus();
};
}, [open]);
return (
<dialog
ref={dialogRef}
className="modal"
aria-labelledby={titleId}
// Fires when the dialog closes for any reason (Escape, form, close())
onClose={onClose}
// A click whose target is the <dialog> itself landed on the backdrop
onClick={(event) => {
if (event.target === event.currentTarget) onClose();
}}
>
<div className="modal__body">
<h2 id={titleId}>{title}</h2>
{children}
<button type="button" className="modal__close" onClick={onClose}>
Close
</button>
</div>
</dialog>
);
}.modal {
padding: 0; /* so backdrop clicks are easy to detect */
border: 0;
border-radius: 1rem;
max-width: min(32rem, 100% - 2rem);
box-shadow: 0 20px 50px rgb(0 0 0 / 0.3);
}
.modal::backdrop {
background: rgb(0 0 0 / 0.5);
}
.modal__body {
padding: 1.5rem;
}
/* Stop the page behind from scrolling while a modal is open */
body:has(dialog[open]) {
overflow: hidden;
}Modal yang mudah diakses perlukan empat perkara: fokus berpindah masuk bila ia dibuka, pengguna papan kekunci dan pembaca skrin tak boleh merayau ke belakangnya, Escape menutupnya, dan fokus kembali ke butang yang membukanya. Membina semua itu secara manual mudah tersilap. dialog.showModal() beri tiga yang pertama secara percuma; React uruskan yang terakhir dalam cleanup effect.
Baris demi baris
showModal()letak dialog dalam top layer pelayar (di atas semuaz-index), jadikan semua yang lain inert (tak boleh diklik atau difokus), fokus elemen boleh-fokus pertama di dalamnya, dan tutup bila Escape ditekan.- Effect hanya berjalan bila
openbernilai true. Cleanupnya berjalan bilaopenmenjadi false (atau component di-unmount): ia tutup dialog jika perlu dan kembalikan fokus kepada elemen yang difokus sebelum itu. onCloseialah eventcloseasli. Escape menutup dialog dalam pelayar, jadi kita beritahu React — jika tidak, stateopenReact dan dialog sebenar tidak akan selari.useId()cipta id unik untuk tajuk, danaria-labelledbymenyebabkan pembaca skrin umumkan "Delete project, dialog" bila ia dibuka.showModal()sudah membawa maksudaria-modal.- Dialog tiada padding dan kandungan berada dalam
.modal__body, jadi klik yangtargetnya ialah<dialog>itu sendiri hanya boleh berlaku pada backdrop. Begitulah cara "klik di luar untuk tutup" berfungsi.
Letak autoFocus pada elemen yang patut menerima fokus dahulu (untuk tindakan yang memadam, biasanya butang "Cancel" yang selamat). Uji dengan papan kekunci sahaja: Tab patut berpusing di dalam dialog, Escape patut menutupnya, dan fokus patut kembali ke butang pembuka.
Cara guna
import { useState } from "react";
import { Modal } from "./Modal.jsx";
import "./modal.css";
export default function App() {
const [open, setOpen] = useState(false);
return (
<>
<button type="button" onClick={() => setOpen(true)}>
Delete project…
</button>
<Modal open={open} onClose={() => setOpen(false)} title="Delete project?">
<p>This removes the project from this browser. It can't be undone.</p>
<button type="button" autoFocus onClick={() => setOpen(false)}>Cancel</button>{" "}
<button type="button" onClick={() => { console.log("deleted"); setOpen(false); }}>
Delete
</button>
</Modal>
</>
);
}