Skip to content

Intermediate · JavaScript in the browser: DOM and events · topic 37 of 64 · 35 min

Forms with JavaScript

Explanation

JavaScript can take over a form: listen for the submit event, stop the page reload, and read every field at once with FormData. Then you check your own rules (a 10-digit mobile number, a 6-digit PIN code, matching passwords), show clear errors next to each field, and disable the button while the data is being sent.

Taking over the submit

When a user presses the submit button, the form fires a submit event. By default, the browser sends the data and loads a new page. To check the data first, or to send it with fetch, listen for submit and call event.preventDefault().

html
<form id="signup" novalidate>
  <label for="mobile">Mobile number</label>
  <input id="mobile" name="mobile" type="tel" inputmode="numeric"
         autocomplete="tel" required aria-describedby="mobile-error">
  <p id="mobile-error" class="error"></p>

  <label for="pin">PIN code</label>
  <input id="pin" name="pin" inputmode="numeric" required aria-describedby="pin-error">
  <p id="pin-error" class="error"></p>

  <button type="submit">Create account</button>
</form>
Each input has a name (for FormData) and points to its own error box

novalidate on the form turns off the browser's own error bubbles, because we will show our own messages. Keep required and type in the HTML anyway: they help screen readers and phone keyboards.

Reading all values with FormData

new FormData(form) collects the value of every field that has a name attribute. Object.fromEntries() turns it into a plain object like { mobile: '9876543210', pin: '560001' }. All values are strings.

javascript
const form = document.querySelector('#signup');

form.addEventListener('submit', (event) => {
  event.preventDefault(); // stop the page reload
  const data = Object.fromEntries(new FormData(form));
  const errors = validateForm(data);
  showErrors(form, errors);
  if (Object.keys(errors).length === 0) {
    sendForm(form, data);
  }
});
The full flow: stop, read, check, show errors, send
Think of it like this: Think of the clerk at a bank counter. Before your form goes to the cashier, the clerk checks every box: is the account number complete, is the date filled in, does the signature match? If something is wrong, the clerk points to that exact box. Your validation code is that clerk.

Your own validation rules

FieldRuleCheck in JavaScript
Mobile (India)10 digits, starts with 6, 7, 8 or 9/^[6-9]\d{9}$/.test(mobile)
PIN code6 digits, does not start with 0/^[1-9]\d{5}$/.test(pin)
PasswordAt least 8 characterspassword.length >= 8
Confirm passwordSame as passwordconfirm === password
javascript
function validateForm(data) {
  const errors = {};
  if (!/^[6-9]\d{9}$/.test(data.mobile)) {
    errors.mobile = 'Enter a 10-digit mobile number';
  }
  if (!/^[1-9]\d{5}$/.test(data.pin)) {
    errors.pin = 'Enter a 6-digit PIN code';
  }
  if (data.password.length < 8) {
    errors.password = 'Use at least 8 characters';
  }
  if (data.confirm !== data.password) {
    errors.confirm = 'The passwords do not match';
  }
  return errors; // {} means everything is fine
}
A pure function: data in, errors object out. Easy to test.

Showing errors next to the field

Put each message in the box that belongs to its field. aria-describedby on the input links it to that box, so a screen reader reads the error together with the label. aria-invalid="true" tells it the field is wrong. setCustomValidity(message) marks the field invalid for the browser too, and setCustomValidity('') clears it.

javascript
function showErrors(form, errors) {
  for (const input of form.querySelectorAll('input')) {
    const message = errors[input.name] || '';
    const box = document.getElementById(input.getAttribute('aria-describedby'));
    if (box) box.textContent = message;
    input.setCustomValidity(message); // '' means valid
    input.setAttribute('aria-invalid', message ? 'true' : 'false');
  }
}
Each error appears right under its own field

Disabling the button while sending

javascript
async function sendForm(form, data) {
  const button = form.querySelector('button[type=submit]');
  button.disabled = true;           // stop double clicks
  button.textContent = 'Sending...';
  try {
    await fetch('/api/signup', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
  } finally {
    button.disabled = false;        // runs on success AND on error
    button.textContent = 'Create account';
  }
}
finally makes sure the button comes back even if sending fails
Careful: Checking in the browser only helps the user. It is not security: anyone can turn off your JavaScript or send data straight to the server. The server must check every value again.
Tip: On phones, type="tel" or inputmode="numeric" opens the number keypad, and autocomplete="tel" lets the phone fill the number. Small things like this make forms much faster to fill.

Watch the lesson

Forms with JavaScript · 1/7

Forms + JavaScript

Forms are where users give you data: sign-ups, bookings, payments. JavaScript makes them friendly.

Prefer a video by a teacher? Find free videos on “javascript form validation formdata tutorial for beginners” on YouTube ↗

In real life: A railway or bus booking form

A ticket booking form asks for passenger name, mobile number and PIN code. When you press "Continue", JavaScript stops the reload, reads all fields with FormData, and finds the mobile number has only 9 digits. It shows "Enter a 10-digit mobile number" right under that box and moves focus there. Once everything is right, the button changes to "Please wait..." and is disabled, so a nervous double tap does not book two tickets.

Practice (0/3 done)

Task 1 · Code · HTML

Write the markup for a sign-up form that JavaScript can work with. Inside a <form id="signup">: a <label for="mobile">, an <input id="mobile" name="mobile" type="tel" required> with aria-describedby pointing to an error box such as <p id="mobile-error">, and a submit <button>. No script is needed here.

Task 2 · Code · Javascript

Write validateForm(data). data has name, mobile, pin, password and confirm (all strings). Return an errors object with a message string for each wrong field, and {} when all is fine. Rules: name is not empty after trim(); mobile is 10 digits starting with 6, 7, 8 or 9; pin is 6 digits not starting with 0; password has at least 8 characters; confirm equals password. Use the keys name, mobile, pin, password, confirm.

Task 3 · Practice

Build a full sign-up form in your browser with name, mobile, PIN code, password and confirm password. On submit: stop the reload, read the values with FormData, run your validateForm, show each error under its field, and move focus to the first wrong field. When everything is right, disable the button for 2 seconds (use setTimeout to pretend you are sending) and then show "Account created".

Quick quiz

Question 1

1.Which attribute must a field have so that new FormData(form) includes it?

Question 2

2.Which value passes the test /^[6-9]\d{9}$/?

Question 3

3.What does aria-describedby="pin-error" on an input do?

Question 4

4.Why disable the submit button while the form is being sent?

Question 5

5.Your JavaScript checks the PIN code. Does the server still need to check it?

Read more (free)

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

← Previous: Events: reacting to the user