Skip to content

Form Validation

HTML5 provides built-in form validation attributes that handle common validation patterns without JavaScript.

<form id="signupForm">
<input type="text" id="email" name="email">
<span id="emailError" class="error"></span>
<input type="text" id="password" name="password">
<span id="passwordError" class="error"></span>
<button type="submit">Sign Up</button>
</form>
<script>
document.getElementById('signupForm').addEventListener('submit', function(e) {
e.preventDefault();
let valid = true;
const email = document.getElementById('email').value;
const emailError = document.getElementById('emailError');
if (!email) {
emailError.textContent = 'Email is required';
valid = false;
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
emailError.textContent = 'Invalid email format';
valid = false;
} else {
emailError.textContent = '';
}
const password = document.getElementById('password').value;
const passwordError = document.getElementById('passwordError');
if (!password) {
passwordError.textContent = 'Password is required';
valid = false;
} else if (password.length < 8) {
passwordError.textContent = 'Password must be at least 8 characters';
valid = false;
} else {
passwordError.textContent = '';
}
if (valid) {
this.submit();
}
});
</script>

Problems:

  • Lots of boilerplate JavaScript
  • Must manually prevent form submission
  • Error messages managed separately
  • Inconsistent UX across implementations
<form>
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
required
placeholder="user@example.com"
>
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
required
minlength="8"
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
title="Must contain at least one number, one uppercase and lowercase letter, and be at least 8 characters"
>
<button type="submit">Sign Up</button>
</form>

Benefits:

  • Zero JavaScript required for basic validation
  • Browser-native error messages
  • Prevents submission automatically
  • Consistent UX patterns
AttributeDescriptionExample
requiredField must have a value<input required>
typeValidates format (email, url, number)<input type="email">
minlengthMinimum character count<input minlength="8">
maxlengthMaximum character count<input maxlength="100">
minMinimum numeric value<input type="number" min="0">
maxMaximum numeric value<input type="number" max="100">
stepValid numeric intervals<input type="number" step="0.01">
patternRegex pattern to match<input pattern="[A-Za-z]+">
titleCustom hint for pattern errors<input pattern="..." title="Letters only">
/* Valid input */
input:valid {
border-color: green;
}
/* Invalid input */
input:invalid {
border-color: red;
}
/* Only show invalid after interaction */
input:not(:placeholder-shown):invalid {
border-color: red;
}
/* Invalid when focused */
input:focus:invalid {
outline-color: red;
}
<input
type="email"
id="email"
required
oninvalid="this.setCustomValidity('Please enter a valid email address')"
oninput="this.setCustomValidity('')"
>

Or with JavaScript:

const input = document.getElementById('email');
input.addEventListener('invalid', () => {
input.setCustomValidity('Please enter a valid email address');
});
input.addEventListener('input', () => {
input.setCustomValidity('');
});

HTML5 validation is supported in all modern browsers.

  • Native validation announces errors to screen readers
  • aria-describedby can link to additional help text
  • Error messages appear on focus
  • Form won’t submit until all validations pass

Use native validation for:

  • Required fields
  • Email, URL, number format validation
  • Length constraints
  • Simple pattern matching

Add JavaScript validation for:

  • Async validation (checking username availability)
  • Cross-field validation (password confirmation)
  • Complex business logic
  • Custom error message positioning