Introduction
ommon cases (required fields, email format, number ranges) without JavaScript. For complex cases, you add JavaScript on top.
Validation should happen on both client and server. Client-side validation improves UX; server-side validation is the security boundary.
HTML5 Constraint Validation
The browser validates automatically when you use the right input types and attributes:
<form>
<!-- Required field -->
<input type="text" name="name" required>
<!-- Email format -->
<inpu" name="email" required>
<!-- URL format -->
<input type="url" name="website">
<!-- Number with range -->
<input type="number" name="age" min="18" max="120" required>
<!-- Password with minimum length -->
<input type="password" name="password" minlength="8" required>
<!-- Pattern matching -->
<input type="text" name="zipcode" pattern="[0-9]{5}" title="5-digit ZIP code">
<!-- Phone number -->
<input type="tel" name="phone" pattern="[0-9]{10,11}">
<button type="submit">Submit</button>
</form>
The browser shows error messages automatically when the form is submitted. No JavaScript needed for basic validation.
The Constraint Validation API
Use JavaScript to check validity programmatically and customize error messages:
const form = document.getElementById('myForm');
const emailInput = document.getElementById('email');
// Check if a field is valid
emailInput.checkValidity() // returns true/false
// Get the validation state
emailInput.validity.valueMissing // true if required and empty
emailInput.validity.typeMismatch // true if wrong type (e.g., not an email)
emailInput.validity.patternMismatch // true if pattern doesn't match
emailInput.validShort // true if below minlength
emailInput.validity.tooLong // true if above maxlength
emailInput.validity.rangeUnderflow // true if below min
emailInput.validity.rangeOverflow // true if above max
emailInput.validity.valid // true if all constraints pass
// Set a custom error message
emailInput.setCustomValidity('Please enter a company email address');
emailInput.setCustomValidity(''); // clear the error
Custom Validation with Inline Error Messages
Show errors next to each field instead of browser popups:
<form id="contactForm" novalidate>
<div class="field">
<label for="name">Name *</label>
<input id="name" type="text" required minlength="2">
<span class="error" aria-live="polite"></span>
</div>
<div class="field">
<label for="email">Email *</label>
<input id="email" type="email" required>
<span class="error" aria-live="polite"></span>
</div>
<div class="field">
<label for="phone">Phone</label>
<tern="[0-9]{10,11}">
<span class="error" aria-live="polite"></span>
</div>
<button type="submit">Submit</button>
</form>
// form-validation.js
const form = document.getElementById('contactForm');
// Custom error messages per validation type
const errorMessages = {
valueMissing: 'This field is required.',
typeMismatch: {
email: 'Please enter a valid email address.',
url: 'Please enter a valid URL.',
},
patternMismatch:d format.',
tooShort: (input) => `Minimum ${input.minLength} characters required.`,
tooLong: (input) => `Maximum ${input.maxLength} characters allowed.`,
rangeUnderflow: (input) => `Minimum value is ${input.min}.`,
rangeOverflow: (input) => `Maximum value is ${input.max}.`,
};
function getErrorMessage(input) {
const validity = input.validity;
if (validity.valueMissing) return errorMessages.valueMissing;
if (validmatch[input.type] || 'Invalid format.';
if (validity.patternMismatch) return input.title || errorMessages.patternMismatch;
if (validity.tooShort) return errorMessages.tooShort(input);
if (validity.tooLong) return errorMessages.tooLong(input);
if (validity.rangeUnderflow) return errorMessages.rangeUnderflow(input);
if (validity.rangeOverflow) return errorMessages.rangeOverflow(input);
return 'Invalid value.';
}
function showError(input, message) {
const errorEl = input.nextElementSibling;
input.setAttribute('aria-invalid', 'true');
errorEl.textContent = message;
errorEl.style.display = 'block';
}
function clearError(input) {
const errorEl = input.nextElementSibling;
input.removeAttribute('aria-invalid');
errorEl.textContent = '';
errorEl.style.display = 'none';
}
sendContactEmail(req.body);
res.json({ success: true });
}
);
Resources
- MDN: Client-side form validation
- MDN: Constraint Validation API
- React Hook Form
- Zod
- express-validator cript // Express.js with express-validator import { body, validationResult } from ’express-validator';
app.post(’/api/contact’, body(’name’).trim().isLength({ min: 2, max: 50 }).escape(), body(’email’).isEmail().normalizeEmail(), body(‘message’).trim().isLength({ min: 10, max: 1000 }).escape(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// Process valid data
valid email address.
```textServer-Side Validation (Always Required)
Client-side validation can be bypassed. Always validate on the server:
<input type="password" {...register('confirm')} />
{errors.confirm && <p>{errors.confirm.message}</p>}
<button type="submit">Sign Up</button>
</form>
);
}
```text
## Accessibility Requirements
Form validation must be accessible:
<input id=“email” type=“email” aria-describedby=“email-error” aria-invalid=“true” required
Please enter a const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), });
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...register('name')} />
{errors.name && <p>{errors.name.message}</p>}
<input type="email" {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
<input type="password" {...register('password')} />
{errors.password && <p>{errors.passwoz.string().min(2, 'Minimum 2 characters').max(50),
email: z.string().email('Invalid email address'),
age: z.number().min(18, 'Must be 18 or older').max(120),
password: z.string()
.min(8, 'Minimum 8 characters')
.regex(/[A-Z]/, 'Must contain uppercase letter')
.regex(/[0-9]/, 'Must contain a number'),
confirm: z.string(),
}).refine(data => data.password === data.confirm, { message: “Passwords don’t match”, path: [‘confirm’], });
function SignupForm() { ole=“alert”>{errors.email.message}
)}
Comments