Accessing Form Elements
There are several ways to reference a form and its inputs:
// By ID (most common)
const form = document.getElementById('signup-form');
const emailEl = document.getElementById('email');
// Via form.elements (named collection)
// Works with name or id attribute
const nameInput = form.elements['username'];
const emailInput = form.elements['email'];
// .elements also supports numeric index
console.log(form.elements[0]); // first input in the form
// Form reference from an input
const parentForm = emailEl.form; // the <form> that owns this input
// All inputs matching a type
const checkboxes = form.querySelectorAll('input[type="checkbox"]');
Reading Input Values
// Text, email, password, number, textarea β use .value
const username = document.getElementById('username').value;
const email = document.getElementById('email').value.trim();
// Checkbox β use .checked (boolean)
const agreeBox = document.getElementById('agree');
console.log(agreeBox.checked); // true | false
// Radio buttons β find the checked one
const genderInputs = document.querySelectorAll('input[name="gender"]');
let selectedGender = '';
genderInputs.forEach(radio => {
if (radio.checked) selectedGender = radio.value;
});
// Select (dropdown) β .value gives selected option's value
const country = document.getElementById('country').value;
// Multi-select β iterate options
const multiSelect = document.getElementById('skills');
const selected = Array.from(multiSelect.options)
.filter(opt => opt.selected)
.map(opt => opt.value);
username β "alice" email β "alice@example.com" checked β true gender β "female" country β "US"
The Submit Event and preventDefault
const form = document.getElementById('login-form');
form.addEventListener('submit', (e) => {
e.preventDefault(); // stops the browser from reloading / navigating
const email = form.elements['email'].value.trim();
const password = form.elements['password'].value;
if (!email || !password) {
showError('All fields are required.');
return;
}
// Send data to server (AJAX / Fetch)
submitLogin({ email, password });
});
function showError(message) {
const errorEl = document.getElementById('form-error');
errorEl.textContent = message;
errorEl.style.display = 'block';
}
function submitLogin(data) {
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(res => res.json()).then(handleResponse);
}
Call e.preventDefault() at the very start of your submit handler, before any validation logic. If an error is thrown before it runs, the browser will navigate away and your error handling code never executes.
Real-Time Validation with the input Event
const emailInput = document.getElementById('email');
const emailError = document.getElementById('email-error');
// Validate as the user types
emailInput.addEventListener('input', () => {
const value = emailInput.value.trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (value === '') {
showFieldError(emailInput, emailError, 'Email is required');
} else if (!emailRegex.test(value)) {
showFieldError(emailInput, emailError, 'Enter a valid email address');
} else {
clearFieldError(emailInput, emailError);
}
});
function showFieldError(input, errorEl, message) {
input.classList.add('invalid');
input.classList.remove('valid');
errorEl.textContent = message;
errorEl.style.display = 'block';
}
function clearFieldError(input, errorEl) {
input.classList.remove('invalid');
input.classList.add('valid');
errorEl.textContent = '';
errorEl.style.display = 'none';
}
HTML5 Constraint Validation API
HTML5 added built-in validation attributes (required, minlength, pattern, type="email", etc.). JavaScript can query their state and customise messages:
const input = document.getElementById('username');
// checkValidity() β returns true if all constraints pass
console.log(input.checkValidity()); // false if required + empty
// validity object β individual flags
const v = input.validity;
console.log(v.valueMissing); // true if required and empty
console.log(v.tooShort); // true if below minlength
console.log(v.patternMismatch);// true if pattern attr fails
console.log(v.typeMismatch); // true for wrong type (email, url, etc.)
// validationMessage β browser's localised error string
console.log(input.validationMessage); // "Please fill in this field."
// setCustomValidity β override with your own message
input.setCustomValidity('Username must not contain spaces');
// Reset to built-in validation (empty string = valid)
input.setCustomValidity('');
// form.checkValidity() β validates ALL fields
const form = document.getElementById('signup-form');
if (!form.checkValidity()) {
form.reportValidity(); // shows native tooltips
}
FormData API
const form = document.getElementById('profile-form');
form.addEventListener('submit', (e) => {
e.preventDefault();
// Automatically reads ALL named inputs, including files
const formData = new FormData(form);
// Read individual values
console.log(formData.get('username'));
console.log(formData.get('avatar')); // File object
// Iterate all entries
for (const [name, value] of formData.entries()) {
console.log(name, value);
}
// Modify programmatically
formData.set('timestamp', Date.now());
formData.delete('honeypot'); // remove spam trap field
// Send as multipart/form-data (perfect for file uploads)
fetch('/api/profile', {
method: 'POST',
body: formData // DO NOT set Content-Type header β browser sets it
});
// Convert to plain object
const data = Object.fromEntries(formData.entries());
console.log(data);
});
File Inputs
const fileInput = document.getElementById('avatar');
fileInput.addEventListener('change', () => {
const files = fileInput.files; // FileList (array-like)
const file = files[0]; // first selected file
if (!file) return;
// File metadata
console.log(file.name); // "photo.jpg"
console.log(file.size); // bytes
console.log(file.type); // "image/jpeg"
// Validate size (max 2 MB)
const MAX_SIZE = 2 * 1024 * 1024;
if (file.size > MAX_SIZE) {
showError('File must be under 2 MB');
fileInput.value = ''; // clear selection
return;
}
// Preview image with FileReader
const reader = new FileReader();
reader.onload = (e) => {
document.getElementById('preview').src = e.target.result;
};
reader.readAsDataURL(file);
});
Always validate and sanitize data on the server. JavaScript validation can be bypassed by disabling JavaScript or crafting raw HTTP requests. Treat client-side validation as a UX convenience, not a security measure.
Common Validation Patterns Table
| Rule | Regex / API | Example |
|---|---|---|
| Required field | value.trim() !== '' | Any non-empty input |
/^[^\s@]+@[^\s@]+\.[^\s@]+$/ | user@example.com | |
| Strong password | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/ | Min 8 chars, upper, lower, digit |
| Phone (US) | /^\+?1?\s?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/ | (555) 123-4567 |
| URL | type="url" or URL constructor | https://example.com |
| Numeric range | min/max attributes + validity.rangeUnderflow | Age 18β120 |
ποΈ Practical Exercise
Build a registration form with real-time validation:
- Fields: username (3β20 chars, alphanumeric), email, password (min 8 chars, 1 uppercase, 1 digit), confirm password.
- Show inline error messages as the user types using the
inputevent. - The submit button should be disabled until all fields are valid.
- On valid submit, log the form data as a plain object using
Object.fromEntries(new FormData(form)).
π₯ Challenge Exercise
Build a multi-step form wizard with three steps (Personal Info, Account Details, Review & Submit). Each step validates before advancing. Show a progress bar. On the final step, display all entered data in a review card. Use a single <form> element and manage visibility of steps with CSS classes. Submit using FormData and fetch.
Summary
π Summary
- Access inputs via
getElementById,form.elements[name], orquerySelector. - Read values with
.value; checkboxes/radios use.checked. - Always call
e.preventDefault()in submit handlers to prevent page reload. - Use the
inputevent for live validation andchangefor on-commit validation. - The Constraint Validation API (
checkValidity(),validity,setCustomValidity()) leverages HTML5 attributes. FormDataautomatically collects all named inputs including files.- Never rely solely on client-side validation β always validate on the server too.
Interview Questions
- Why do we call
e.preventDefault()on form submit? - What is the difference between the
inputevent and thechangeevent? - How does the Constraint Validation API differ from custom JavaScript validation?
- When should you use
FormDatainstead of reading values manually? - Why is client-side validation insufficient for security?
Frequently Asked Questions
Call form.reset() to restore all inputs to their default values (as defined in the HTML). This also triggers the reset event so you can clear any custom error states in a reset handler: form.addEventListener('reset', clearAllErrors).
Yes. Add the novalidate attribute to the <form> element: <form novalidate>. This disables the browser's built-in UI but the underlying Constraint Validation API (checkValidity(), validity.*) still works, letting you implement fully custom error UI while still leveraging HTML5 validation logic.
formData.get('interests') only returns the first checked value. Use formData.getAll('interests') to get an array of all checked values for inputs sharing the same name attribute. This is the correct way to handle multi-value fields like checkbox groups and multi-select elements.