Kodokon kodokon.com

Advanced forms and native validation

Leverage required, pattern, inputmode, and the Constraint Validation API for robust forms without any external library.

10 min · 3 questions

Open this lesson in Kodokon

The browser ships with a complete validation engine that many teams reimplement in JavaScript out of habit. The building blocks: required (mandatory field), the types (email, url, date) that validate format, minlength/maxlength, min/max/step for numeric values, and pattern, which accepts a regular expression. Crucial detail: pattern is implicitly anchored - the entire value must match, as if the regex were wrapped in ^ and $. Add autocomplete with the standardized values (postal-code, email, tel): it is both UX and accessibility.

HTML
<label for="zip">Postal code</label>
<input id="zip" name="zip" type="text"
       required pattern="[0-9]{5}"
       inputmode="numeric"
       autocomplete="postal-code">
Native validation: format, requirement, matching keyboard

The inputmode attribute is often misunderstood: it validates nothing. It merely tells the mobile system which virtual keyboard to display (numeric, tel, email, decimal, search). That is why the type="text" + inputmode="numeric" + pattern combo is the recommended pattern for numeric codes: you get the right keyboard without inheriting the behaviors of type="number".

CSS
input:user-invalid {
  border: 2px solid #b3261e;
}
input:user-valid {
  border: 2px solid #1b5e20;
}
:user-invalid only kicks in after the user has interacted

On the CSS side, prefer :user-invalid over :invalid: the latter applies as soon as the page loads, painting red fields the user has not even touched yet. On the JavaScript side, the Constraint Validation API rounds out the toolkit: checkValidity() triggers the check, the validity object exposes the exact cause (patternMismatch, valueMissing, tooShort), and setCustomValidity() lets you replace the browser's generic messages with your own.

JAVASCRIPT
const zip = document.querySelector("#zip");
zip.addEventListener("input", () => {
  if (zip.validity.patternMismatch) {
    zip.setCustomValidity("Enter 5 digits.");
  } else {
    zip.setCustomValidity("");
  }
});
Custom message via the validation API

Knowledge check

Make sure you remember the key points of this lesson.

  1. What exactly does inputmode="numeric" do?
    • Reject any non-numeric input
    • Convert the field's value into a number
    • Suggest a numeric keyboard on mobile, without validating anything
  2. With pattern="[0-9]{5}", is the value "abc12345" accepted?
    • Yes, because it does contain 5 consecutive digits
    • No, because pattern requires the entire value to match
    • Yes, if the field is not required
  3. Why prefer :user-invalid over :invalid for styling errors?
    • :user-invalid only applies after the user has interacted
    • :invalid is not supported by recent browsers
    • :user-invalid also validates disabled fields