Focus trap kiểu "bắt sự kiện keydown, nếu là Tab ở phần tử cuối thì focus về phần tử đầu" đúng trên demo nhưng hỏng ở thực tế:
- Danh sách phần tử focus được bị tính sai. Selector cứng (
button, a[href], input) bỏ sót[contenteditable],[tabindex]tuỳ biến,<audio controls>, iframe; và tính nhầm cả phần tử đangdisabled,hidden,display: nonehoặc bị ẩn bởi cha. - Danh sách thay đổi khi modal mở. Nội dung render bất đồng bộ hoặc phần tử ẩn/hiện theo state → phải tính lại tại thời điểm nhấn Tab, không cache lúc mở.
- Không chặn được đường ra ngoài viewport: thanh địa chỉ, DevTools, tab trình duyệt vẫn tới được — đó là hành vi đúng, không nên cố chặn.
- Screen reader không đi bằng Tab. Chế độ đọc theo con trỏ ảo (NVDA/JAWS) vẫn lướt được nội dung nền dù Tab đã bị chặn.
inert là cách chuẩn cho vế cuối: đặt lên phần còn lại của trang thì cả cây con mất focus, mất sự kiện chuột, và bị gỡ khỏi accessibility tree.
js
document.getElementById('app').inert = true // khi modal mởaria-hidden="true" chỉ giấu khỏi screen reader, không chặn focus — đặt lên vùng chứa phần tử focus được là lỗi thường bị axe bắt. Chỉ dùng cho nội dung thuần trang trí (icon bên cạnh chữ đã có nhãn).
Dùng <dialog> + showModal() thì trình duyệt tự làm phần trap và inert nền — nên ưu tiên trước khi tự viết.