Modal adalah kotak yang muncul di atas halaman dan menghentikan halaman itu sampai kamu menjawab. Overlay adalah sebutan umum untuk apa pun yang tampil menumpuk di atas halaman; modal adalah overlay yang paling "keras" karena halaman di belakangnya gak bisa dipakai. Bayangin meja resepsionis Bu Sri. Sebelum menulis namamu di buku tamu, Bu Sri menahan pulpennya dan bertanya: "Kamar Teras, 12 sampai 14 Des, Rp 700.000, betul?" Selama dia bertanya, kamu gak bisa jalan-jalan melihat kamar lain. Kamu cuma bisa menjawab "ya" atau "batal", dan setelah itu kamu kembali tepat ke tempatmu berdiri tadi.
🧪 Tebakan cepat: Tim Bu Sri membuat modal konfirmasi dari
<div>biasa dengan latar gelap di belakangnya. Pak Hadi, yang memakai keyboard, menekan "Pesan kamar, bayar di tempat", lalu menekan Tab beberapa kali. Fokusnya tetap di dalam modal? Jawabannya ada di bagian "Yang dikerjakan showModal() untukmu".
Modal konfirmasi pesanan
Di halaman Homestay Kabut, tombol "Pesan kamar, bayar di tempat" sekarang membuka satu modal: ringkasan pesanan dan dua pilihan. Browser sudah punya elemen untuk ini, <dialog>, dan cara membukanya sebagai modal adalah showModal().
<button class="book" id="book">Pesan kamar, bayar di tempat</button>
<dialog class="modal" aria-labelledby="modal-title">
<form method="dialog">
<button class="close-btn" value="batal" aria-label="Tutup">×</button>
<h2 id="modal-title">Pesan Kamar Teras?</h2>
<p>12 sampai 14 Des · 2 malam · 2 tamu</p>
<p class="total">Total Rp 700.000, bayar di tempat</p>
<p class="note">Gratis batal sampai 3 hari sebelum check-in.</p>
<div class="actions">
<button value="batal" class="cancel">Batal</button>
<button value="pesan" class="book">Ya, pesan</button>
</div>
</form>
</dialog>
<style>
.modal { max-width: 20rem; padding: 24px; border: 0; border-radius: 12px; color: #1f2937; }
.modal::backdrop { background: rgb(31 41 55 / 0.6); } /* #1f2937, 60% */
html:has(dialog:modal) { overflow: hidden; } /* halaman di belakang gak ikut scroll */
</style>
<script>
const book = document.querySelector('#book');
const modal = document.querySelector('.modal');
book.addEventListener('click', () => modal.showModal());
modal.addEventListener('close', () => {
if (modal.returnValue === 'pesan') {
// kirim pesanan ke Bu Sri
}
});
</script>
<dialog>adalah elemen bawaan HTML untuk kotak dialog. TanpashowModal(), dia tersembunyi.aria-labelledby="modal-title"menunjukidjudulnya, jadi screen reader mengumumkan "Pesan Kamar Teras?, dialog" saat modal terbuka.<form method="dialog">: tombol di dalamnya menutup dialog tanpa JavaScript, danvaluetombol yang ditekan jadimodal.returnValue("pesan"atau"batal").- Tombol
×diberiaria-label="Tutup", karena tanda × sendirian dibacakan sebagai nama simbolnya ("kali" atau "times"), bukan "tutup". ::backdropadalah lapisan gelap di antara modal dan halaman. Isinya#1f2937dengan opacity 60%.showModal()membuka dialog sebagai modal. Eventclosejalan setiap kali dialog tertutup, lewat tombol atau Esc.
Yang dikerjakan showModal() untukmu
Modal yang benar punya tiga syarat aksesibilitas: punya nama (aria-labelledby), fokus terkurung di dalamnya (focus trap: Tab gak keluar ke halaman di belakang), dan bisa ditutup dengan Esc. Kami membandingkan showModal() dengan <div> plus overlay di Chrome, Safari, dan Firefox:
| Perilaku | <dialog> + showModal() |
<div> + overlay |
|---|---|---|
| Saat terbuka, fokus pindah ke | elemen pertama yang bisa difokus di modal (di sini tombol ×) | tetap di tombol "Pesan" di belakang |
| Tab | gak pernah mendarat di halaman belakang | tembus ke halaman belakang (di sini "Fasilitas kamar", di bawah tombol Pesan) |
| Esc | menutup modal | gak terjadi apa-apa |
| Setelah ditutup, fokus kembali ke | tombol "Pesan" yang membukanya | hilang |
| Scroll halaman di belakang | tetap bergerak, kecuali dikunci | tetap bergerak |
showModal() membuat semua isi halaman di luar dialog inert: gak bisa diklik, gak bisa difokus, dan gak dibacakan screen reader. Itulah focus trap-nya. Setelah tombol terakhir di modal, Tab boleh pindah ke bilah alamat browser lalu kembali ke modal; itu benar, karena WCAG 2.1.2 (No Keyboard Trap) meminta orang selalu bisa keluar. Yang penting, halaman di belakang gak tersentuh.
Jadi jawaban tebakannya: gak. Dengan <div>, fokus mulai dari tombol "Pesan" di belakang overlay, dan Tab tembus ke tombol-tombol halaman yang tertutup lapisan gelap. Pak Hadi gak melihat di mana fokusnya, dan Esc gak menutup apa pun. Itu jawaban tebakannya.
Kunci scroll halaman
Satu hal yang showModal() gak kerjakan: di ketiga browser, memutar roda mouse di atas backdrop tetap menggulung halaman di belakang (di tes kami, 300px jadi 700px). Satu baris CSS menguncinya selama modal terbuka:
html:has(dialog:modal) { overflow: hidden; }
:modal cocok dengan dialog yang dibuka dengan showModal(), dan :has() membuat aturan berlaku di <html> hanya selama dialog itu ada. Dengan aturan ini, posisi scroll tetap 300px.
Kapan modal salah pilih
Modal memaksa orang berhenti, jadi pakai hanya saat berhenti itu memang perlu. Panduan Nielsen Norman Group (Therese Fessenden, 2017): pakai modal untuk peringatan penting yang mencegah kesalahan, dan untuk informasi yang wajib ada sebelum lanjut; jangan untuk informasi yang gak penting, dan jangan untuk keputusan yang butuh informasi di luar modal.
| Di halaman Bu Sri | Pilihan | Kenapa |
|---|---|---|
| Ringkasan sebelum pesanan dikirim | Modal | Satu keputusan pendek: ya atau batal |
| "Gratis batal sampai 3 hari sebelum check-in." | Inline | Dibaca saat memutuskan, harus terlihat di samping harga |
| Nama, nomor HP, jam datang, permintaan khusus | Halaman sendiri | Formulir panjang; di HP 375px keyboard menutup separuh modal |
| "Kamar Teras penuh tanggal 12 Des" | Inline | Error milik tanggal, tampil di dekat tanggal |
| "Pesanan terkirim" | Inline | Kabar, bukan pertanyaan; gak perlu ditutup |
📌 Overlay lain: drawer, popover, toast
- Drawer: panel yang muncul dari samping, misalnya filter kamar di HP. Kalau halaman di belakangnya gak bisa dipakai, dia juga modal, dan bisa dibuat dengan
<dialog>yang sama. - Popover: kotak kecil yang menempel ke tombol, misalnya menu "Bagikan". Halaman tetap bisa dipakai.
- Toast: pesan singkat di pojok yang hilang sendiri, misalnya "Tersalin". Halaman tetap bisa dipakai, dan pesannya jangan berisi tombol penting.
⚠️ Jebakan yang sering ketemu
<div>dengan z-index tinggi sebagai modal. Terlihat seperti modal, tapi Tab tembus ke belakang dan Esc mati. Pakai<dialog>denganshowModal().show()bukanshowModal().show()membuka dialog biasa: halaman di belakang tetap bisa diklik dan di-Tab, dan Esc gak menutupnya.aria-labelledbyyang menunjuk keidyang gak ada atau kosong. Modalnya terbuka tanpa nama.idjudul dan isiaria-labelledbyharus sama persis.- Tombol × tanpa
aria-label. Screen reader membacanya sebagai simbol ("kali"), bukan "tutup". Tambahkanaria-label="Tutup". - Modal di atas modal. Orang kehilangan jejak harus menutup yang mana. Selesaikan dulu modal pertama, atau pindahkan isinya ke halaman.
TL;DR: Modal adalah overlay yang menghentikan halaman sampai orang menjawab. Pakai <dialog> dan buka dengan showModal(): browser memindahkan fokus ke dalam modal, membuat halaman di belakang inert (focus trap), menutup dengan Esc, dan mengembalikan fokus ke tombol yang membukanya. Beri nama dengan aria-labelledby yang menunjuk id judulnya, dan beri tombol × aria-label="Tutup". Kunci scroll dengan html:has(dialog:modal) { overflow: hidden; }. <form method="dialog"> menutup modal tanpa JavaScript. Pakai modal hanya untuk keputusan pendek yang wajib (konfirmasi, peringatan); kebijakan, error di samping kolom, kabar sukses, dan formulir panjang tetap inline atau di halaman sendiri.