Form Validation (Constraint Validation API)
Use HTML's built-in rules (`required`, `type`, `pattern`) with your own accessible error messages — no library.
Live preview Open in Playground
<form id="signup" novalidate>
<div class="field">
<label for="name">Name</label>
<input id="name" name="name" required minlength="2"
autocomplete="name" aria-describedby="name-error">
<p class="error" id="name-error"></p>
</div>
<div class="field">
<label for="email">Email</label>
<input id="email" name="email" type="email" required
autocomplete="email" aria-describedby="email-error">
<p class="error" id="email-error"></p>
</div>
<div class="field">
<label for="postcode">Postcode</label>
<input id="postcode" name="postcode" required inputmode="numeric"
pattern="[0-9]{5}" data-pattern-message="Malaysian postcodes have 5 digits."
aria-describedby="postcode-error">
<p class="error" id="postcode-error"></p>
</div>
<button type="submit">Sign up</button>
<p role="status" id="form-status"></p>
</form>const form = document.getElementById("signup");
const statusEl = document.getElementById("form-status");
function messageFor(input) {
const v = input.validity;
if (v.valueMissing) return "Please fill in this field.";
if (v.typeMismatch) return "Please enter a valid " + input.type + " address.";
if (v.tooShort) return "Use at least " + input.minLength + " characters (you typed " + input.value.length + ").";
if (v.patternMismatch) return input.dataset.patternMessage || "Please match the requested format.";
return input.validationMessage; // anything else: the browser's own message
}
function showError(input) {
const errorEl = document.getElementById(input.id + "-error");
const valid = input.validity.valid;
input.setAttribute("aria-invalid", String(!valid));
if (errorEl) errorEl.textContent = valid ? "" : messageFor(input);
return valid;
}
form.addEventListener("submit", (event) => {
event.preventDefault();
const fields = Array.from(form.querySelectorAll("input, select, textarea"));
const firstInvalid = fields.filter((field) => !showError(field))[0];
if (firstInvalid) {
firstInvalid.focus();
statusEl.textContent = "";
return;
}
const data = Object.fromEntries(new FormData(form));
statusEl.textContent = "Thanks, " + data.name + "! (Nothing was sent — this is a demo.)";
console.log("Valid data:", data);
});
// Re-check a field as the user fixes it — but only after it was marked invalid
form.addEventListener("input", (event) => {
if (event.target.getAttribute("aria-invalid") === "true") showError(event.target);
});HTML already knows how to validate: required, type="email", minlength, pattern. The Constraint Validation API lets JavaScript read the result (input.validity) so you can show clear messages in your own design, next to each field.
Line by line
novalidateon the form turns off the browser's pop-up bubbles but keeps the rules —validitystill works. We take over the display.input.validityis aValidityStateobject with flags likevalueMissing,typeMismatch,tooShortandpatternMismatch. Each flag maps to one HTML attribute, so each gets its own message.pattern="[0-9]{5}"must match the whole value (it's anchored automatically).data-pattern-messagekeeps the human message next to the rule in the HTML.aria-invalid="true"tells screen readers the field has a problem, andaria-describedbylinks the field to its error text so it's read out when the field is focused.- On submit we validate everything, then move focus to the first invalid field so keyboard and screen-reader users land right where the problem is.
- The
inputlistener only re-checks fields that are already marked invalid, so people aren't told off while they're still typing for the first time.
Client-side isn't security
Browser validation is for a good experience. Anyone can bypass it, so the server must validate the same data again.
Usage
<!-- Any input with an id, a matching "<id>-error" element and
aria-describedby pointing at it gets custom messages. -->
<input id="phone" name="phone" type="tel" required
pattern="01[0-9]-?[0-9]{7,8}"
data-pattern-message="Use a Malaysian mobile number, e.g. 012-3456789."
aria-describedby="phone-error">
<p class="error" id="phone-error"></p>