Hai hướng, chọn theo mức độ kiểm soát giao diện cần có.
Cách 1 — giữ validation của browser, chỉ đổi lời báo lỗi:
js
const pass2 = form.elements.confirmPassword
pass2.addEventListener('input', () => {
pass2.setCustomValidity(
pass2.value === form.elements.password.value ? '' : 'Mật khẩu nhập lại không khớp'
)
})Cách 2 — tắt hẳn UI mặc định, tự dựng thông báo:
html
<form novalidate> <!-- tắt bong bóng của browser -->
<input name="email" type="email" required aria-describedby="email-err">
<p id="email-err" role="alert" hidden></p>
</form>js
form.addEventListener('submit', (e) => {
e.preventDefault()
const email = form.elements.email
const err = document.getElementById('email-err')
const ok = email.checkValidity() // vẫn dùng lại được luật HTML
err.hidden = ok
err.textContent = ok ? '' : 'Email chưa hợp lệ'
email.setAttribute('aria-invalid', String(!ok))
if (ok) form.submit()
})Lưu ý: dựng thông báo lỗi bằng <p> thường thì screen reader không đọc. Cần role="alert" (hoặc aria-live) và aria-describedby nối vào input.
Chốt: novalidate chỉ tắt giao diện mặc định — checkValidity() và validity vẫn hoạt động, nên không phải viết lại luật kiểm tra từ đầu.