Working with Forms
The submit event, preventDefault, and FormData together let JavaScript intercept a form submission, read all its values, and decide what to do with them.
- Intercept a form submission with the submit event and preventDefault
- Read field values using .value, .checked, and .select.value
- Collect all form data with FormData and access fields by name
- Validate inputs and show error messages using setCustomValidity
- Reset a form and manage focus after a JS-driven submission
HTML forms know how to collect data and send it to a server without any
JavaScript at all. Adding JavaScript to the mix lets you validate before
sending, give instant feedback, and submit data without a page reload. The key
is intercepting the form's built-in submit event and taking over from there.
The submit event
The submit event fires on the <form> element — not on the submit button —
whenever the user clicks a submit button or presses Enter inside a text field.
Always attach the listener to the form:
const form = document.querySelector('#contact-form');
form.addEventListener('submit', (event) => {
event.preventDefault(); // stop the browser from reloading the page
// Your code runs here
});event.preventDefault() is essential. Without it the browser navigates away
or reloads the page before your handler can do anything useful.
Reading input values
Each input type exposes its current value differently:
const nameInput = document.querySelector('#name');
const emailInput = document.querySelector('#email');
const subscribeCheckbox = document.querySelector('#subscribe');
const countrySelect = document.querySelector('#country');
const name = nameInput.value; // string, possibly empty
const email = emailInput.value;
const subscribed = subscribeCheckbox.checked; // boolean
const country = countrySelect.value; // the value="" of the selected optionvalue returns whatever is currently in the field as a string — even for
type="number". Convert with Number() or parseInt() if you need arithmetic.
FormData — the all-fields shortcut
Reading each input individually works but gets tedious for large forms. The
FormData constructor inspects a form element and collects every named input
in one call:
form.addEventListener('submit', (event) => {
event.preventDefault();
const data = new FormData(form);
// Access a specific field by its name="" attribute
const email = data.get('email');
// Iterate every field
for (const [name, value] of data.entries()) {
console.log(name, value);
}
});HTML form controls must have a name attribute for
FormData to include them. A control without name is invisible to FormData.
FormData also has a getAll(name) method for multi-value inputs like
checkboxes with the same name, and has(name) to test for a key's presence.
Client-side validation
The browser performs built-in validation based on required, type, min,
max, and pattern attributes before the submit event fires. You can
supplement this — or replace it entirely — with JavaScript:
form.addEventListener('submit', (event) => {
event.preventDefault();
const message = document.querySelector('#message');
const text = message.value.trim();
if (text.length < 20) {
message.setCustomValidity('Please write at least 20 characters.');
message.reportValidity(); // shows the browser's built-in error bubble
return; // stop — do not submit
}
// Clear any previous custom error before submitting
message.setCustomValidity('');
submitForm(new FormData(form));
});setCustomValidity('') resets the error. If you forget to clear it, the input
stays permanently invalid even after the user fixes the value.
Client-side validation is for user experience, not security. It runs in the browser and can be bypassed by anyone who knows how to open DevTools or send a raw HTTP request. Always validate again on the server before trusting the data.
Resetting a form
form.reset() clears all inputs back to their default values — equivalent to
clicking a <button type="reset">:
function onSuccess() {
form.reset();
showConfirmation();
}Call reset() after a successful submission so the form is ready for another
entry — especially useful in single-page apps where the form stays visible.
Accessibility: move focus after submission
When a JS-driven form submits successfully, do not just update some text somewhere on the page silently. Users navigating by keyboard or using a screen reader need to know the submission happened:
function showConfirmation() {
const message = document.querySelector('#confirm-message');
message.hidden = false;
message.textContent = 'Your message was sent. We will reply within two business days.';
message.focus(); // move focus so screen reader users hear the announcement
}The confirmation element needs tabindex="-1" to be programmatically
focusable even though it is not a form control:
<div id="confirm-message" tabindex="-1" role="status" hidden></div>role="status" is a polite live region —
screen readers announce its content when it changes, even if focus is elsewhere.
Using both .focus() and role="status" gives the widest coverage across
different assistive technologies.
Where to go next
You have all the building blocks. The Lab: Interactive Form walks you through combining everything in this module — DOM traversal, content manipulation, event handling, and form processing — into one cohesive, accessible contact form.
Event Listeners
Event listeners let JavaScript respond to user actions — clicks, keyboard presses, form submissions — by registering a callback that runs when the event fires.
Lab: Interactive Form
Build a contact form that validates in JavaScript, shows inline error messages without a page reload, and confirms submission with a success message.