Code of the Day
JavaScript in the BrowserJavaScript in the Browser

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.

Web FoundationsDialect appendices7 min read
Recommended first
By the end of this lesson you will be able to:
  • 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 option

value 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);
  }
});

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 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 — 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.

Finished reading? Mark it complete to track your progress.

On this page