<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
.error { color: red; font-size: 0.875rem; }
input:invalid { border-color: red; }
input:valid { border-color: green; }
.required::after { content: " *"; color: red; }
</style>
</head>
<body>
<form id="signup-form" novalidate>
<!-- Text input with validation -->
<div class="form-group">
<label for="username" class="required">Username</label>
<input
type="text"
id="username"
name="username"
required
minlength="3"
maxlength="20"
pattern="[a-zA-Z0-9_]+"
aria-describedby="username-hint username-error"
autocomplete="username"
>
<small id="username-hint">3-20 characters, letters, numbers, underscore only</small>
<span id="username-error" class="error" role="alert"></span>
</div>
<!-- Email input -->
<div class="form-group">
<label for="email" class="required">Email</label>
<input
type="email"
id="email"
name="email"
required
aria-describedby="email-error"
autocomplete="email"
>
<span id="email-error" class="error" role="alert"></span>
</div>
<!-- Password with requirements -->
<div class="form-group">
<label for="password" class="required">Password</label>
<input
type="password"
id="password"
name="password"
required
minlength="8"
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
aria-describedby="password-hint password-error"
autocomplete="new-password"
>
<small id="password-hint">
Must contain at least 8 characters, one uppercase, one lowercase, and one number
</small>
<span id="password-error" class="error" role="alert"></span>
</div>
<!-- Phone number -->
<div class="form-group">
<label for="phone">Phone Number</label>
<input
type="tel"
id="phone"
name="phone"
pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
placeholder="123-456-7890"
aria-describedby="phone-hint"
autocomplete="tel"
>
<small id="phone-hint">Format: 123-456-7890</small>
</div>
<!-- Date of birth -->
<div class="form-group">
<label for="dob" class="required">Date of Birth</label>
<input
type="date"
id="dob"
name="dob"
required
min="1920-01-01"
max="2008-12-31"
aria-describedby="dob-error"
autocomplete="bday"
>
<span id="dob-error" class="error" role="alert"></span>
</div>
<!-- Number input with range -->
<div class="form-group">
<label for="age" class="required">Age</label>
<input
type="number"
id="age"
name="age"
required
min="18"
max="120"
aria-describedby="age-error"
>
<span id="age-error" class="error" role="alert"></span>
</div>
<!-- URL input -->
<div class="form-group">
<label for="website">Website</label>
<input
type="url"
id="website"
name="website"
placeholder="https://example.com"
autocomplete="url"
>
</div>
<!-- Select dropdown -->
<div class="form-group">
<label for="country" class="required">Country</label>
<select id="country" name="country" required aria-describedby="country-error">
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
</select>
<span id="country-error" class="error" role="alert"></span>
</div>
<!-- Radio buttons -->
<fieldset>
<legend class="required">Gender</legend>
<div>
<input type="radio" id="male" name="gender" value="male" required>
<label for="male">Male</label>
</div>
<div>
<input type="radio" id="female" name="gender" value="female">
<label for="female">Female</label>
</div>
<div>
<input type="radio" id="other" name="gender" value="other">
<label for="other">Other</label>
</div>
<span id="gender-error" class="error" role="alert"></span>
</fieldset>
<!-- Checkboxes -->
<fieldset>
<legend>Interests (select all that apply)</legend>
<div>
<input type="checkbox" id="interest-coding" name="interests" value="coding">
<label for="interest-coding">Coding</label>
</div>
<div>
<input type="checkbox" id="interest-design" name="interests" value="design">
<label for="interest-design">Design</label>
</div>
<div>
<input type="checkbox" id="interest-writing" name="interests" value="writing">
<label for="interest-writing">Writing</label>
</div>
</fieldset>
<!-- Textarea -->
<div class="form-group">
<label for="bio">Bio</label>
<textarea
id="bio"
name="bio"
rows="4"
maxlength="500"
aria-describedby="bio-hint"
></textarea>
<small id="bio-hint">Maximum 500 characters</small>
</div>
<!-- Terms acceptance -->
<div class="form-group">
<input
type="checkbox"
id="terms"
name="terms"
required
aria-describedby="terms-error"
>
<label for="terms" class="required">
I agree to the <a href="/terms">Terms and Conditions</a>
</label>
<span id="terms-error" class="error" role="alert"></span>
</div>
<!-- File upload -->
<div class="form-group">
<label for="avatar">Profile Picture</label>
<input
type="file"
id="avatar"
name="avatar"
accept="image/png, image/jpeg"
aria-describedby="avatar-hint"
>
<small id="avatar-hint">PNG or JPEG, max 2MB</small>
</div>
<!-- Submit button -->
<button type="submit">Sign Up</button>
<button type="reset">Clear Form</button>
</form>
<script>
const form = document.getElementById('signup-form');
form.addEventListener('submit', function(e) {
e.preventDefault();
// Clear previous errors
document.querySelectorAll('.error').forEach(el => el.textContent = '');
document.querySelectorAll('input, select').forEach(el => {
el.setAttribute('aria-invalid', 'false');
});
let isValid = true;
// Validate each field
const fields = form.querySelectorAll('input[required], select[required]');
fields.forEach(field => {
if (!field.validity.valid) {
isValid = false;
const errorElement = document.getElementById(field.id + '-error');
if (field.validity.valueMissing) {
errorElement.textContent = 'This field is required';
} else if (field.validity.typeMismatch) {
errorElement.textContent = 'Please enter a valid ' + field.type;
} else if (field.validity.tooShort) {
errorElement.textContent = `Minimum length is ${field.minLength} characters`;
} else if (field.validity.tooLong) {
errorElement.textContent = `Maximum length is ${field.maxLength} characters`;
} else if (field.validity.rangeUnderflow) {
errorElement.textContent = `Minimum value is ${field.min}`;
} else if (field.validity.rangeOverflow) {
errorElement.textContent = `Maximum value is ${field.max}`;
} else if (field.validity.patternMismatch) {
errorElement.textContent = 'Please match the required format';
}
field.setAttribute('aria-invalid', 'true');
field.focus();
}
});
if (isValid) {
console.log('Form is valid!', new FormData(form));
// Submit form
}
});
// Real-time validation
form.querySelectorAll('input, select').forEach(field => {
field.addEventListener('blur', function() {
if (this.value && !this.validity.valid) {
const errorElement = document.getElementById(this.id + '-error');
if (errorElement) {
errorElement.textContent = 'Invalid input';
this.setAttribute('aria-invalid', 'true');
}
}
});
field.addEventListener('input', function() {
if (this.validity.valid) {
const errorElement = document.getElementById(this.id + '-error');
if (errorElement) {
errorElement.textContent = '';
this.setAttribute('aria-invalid', 'false');
}
}
});
});
</script>
</body>
</html>
HTML5 form validation uses attributes like required, pattern, min, max, and type to validate input. I leverage native validation before JavaScript. The <label> element associates text with inputs for accessibility. Input types like email, tel, url, and date provide built-in validation and mobile keyboards. The autocomplete attribute helps users fill forms faster. The novalidate attribute allows custom validation. Using aria-invalid and aria-describedby announces errors to screen readers. Fieldsets group related inputs with <legend> for context. Proper form design improves user experience and conversion rates.