javascript
32 lines · 6 steps
Live password-match validation in the DOM
Two password fields are checked against each other on every keystroke, driving native validity, ARIA state, and the submit button.
Explained by
highlit
1const form = document.querySelector('#signup-form');
2const password = form.querySelector('#password');
3const confirm = form.querySelector('#confirm-password');
4const submit = form.querySelector('button[type="submit"]');
5const feedback = form.querySelector('#confirm-feedback');
6
7function validateMatch() {
8 const a = password.value;
9 const b = confirm.value;
10
11 if (b.length === 0) {
12 confirm.setCustomValidity('');
13 feedback.textContent = '';
14 submit.disabled = a.length === 0;
15 return;
16 }
17
18 if (a !== b) {
19 confirm.setCustomValidity('Passwords do not match');
20 confirm.setAttribute('aria-invalid', 'true');
21 feedback.textContent = 'Passwords do not match';
22 submit.disabled = true;
23 } else {
24 confirm.setCustomValidity('');
25 confirm.removeAttribute('aria-invalid');
26 feedback.textContent = 'Passwords match';
27 submit.disabled = a.length === 0;
28 }
29}
30
31password.addEventListener('input', validateMatch);
32confirm.addEventListener('input', validateMatch);
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1The Constraint Validation API lets you block native form submission by setting a custom validity message on a field.
- 2Mirroring validation state into both visible text and aria-invalid keeps sighted and assistive-tech users equally informed.
- 3Wiring the same handler to input events on both fields keeps the check live and symmetric as either field changes.
Related explainers
javascript
class StarRating { constructor(container, { max = 5, value = 0, onChange } = {}) { this.container = container; this.max = max;
Building an accessible star-rating widget
dom
event-handling
accessibility
Intermediate
7 steps
javascript
const express = require('express'); const EventEmitter = require('events'); const router = express.Router();
Server-Sent Events with Express
server-sent-events
streaming
event-emitter
Advanced
8 steps
javascript
import { useState, useEffect, useCallback } from 'react'; export function useCountdown(initialSeconds) { const [secondsLeft, setSecondsLeft] = useState(initialSeconds);
Building a useCountdown hook in React
custom-hooks
state-management
side-effects
Intermediate
8 steps
javascript
import { useState, useRef } from "react"; export function TagInput({ initialTags = [], onChange }) { const [tags, setTags] = useState(initialTags);
Building a tag input in React
controlled-inputs
state-management
keyboard-handling
Intermediate
8 steps
javascript
import { NavLink, useLocation } from 'react-router-dom'; const NAV_ITEMS = [ { to: '/', label: 'Dashboard', end: true },
Building an accessible Sidebar in React
routing
accessibility
declarative-ui
Intermediate
6 steps
javascript
function escapeHtml(str) { return str.replace(/[&<>"']/g, (ch) => ({ '&': '&', '<': '<',
Safely highlighting search matches in text
html-escaping
regex
search-highlighting
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/live-password-match-validation-in-the-dom-explained-javascript-16ce/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.