Lesson 11 of 12

jQuery Forms and Validation

Forms are where users give you something valuable: an enquiry, an order, a sign-up. Good client-side validation catches mistakes early and explains them clearly, so people finish the form. In this lesson you will read form data with jQuery, add jQuery validation that runs when the form is submitted and as fields are left, and display error messages that work for keyboard and screen-reader users too. The HTML side is covered in HTML Forms.

Reading form values

Field typeHow to read it
Text, email, number, textarea$('#email').val() – always a string; add .trim() to remove spaces
Select$('#city').val() – the selected option’s value
Checkbox$('#terms').prop('checked') – true or false
Radio group$('input[name="size"]:checked').val() – undefined if none is chosen
Several checkboxes$('input[name="topics"]:checked').map((i, el) => el.value).get() – an array
Whole form$('#form').serialize() – a name=value&… string; .serializeArray() – an array of { name, value }

.serialize() produces exactly what a normal form submission would send, so it is perfect for $.post() from the AJAX lesson. Only fields with a name attribute are included, and unticked checkboxes and disabled fields are left out, just as in a real submission.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>serialize(), checkboxes and a disabled button</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    fieldset { margin: 8px 0; }
    code { background: #f3f4f6; padding: 2px 4px; }
  </style>
</head>
<body>
  <form id="workshop">
    <label for="wname">Name</label>
    <input id="wname" name="name" type="text" value="Arun Kumar">
    <fieldset>
      <legend>Batch</legend>
      <label><input type="radio" name="batch" value="morning" checked> Morning</label>
      <label><input type="radio" name="batch" value="evening"> Evening</label>
    </fieldset>
    <fieldset>
      <legend>Topics</legend>
      <label><input type="checkbox" name="topics" value="html" checked> HTML</label>
      <label><input type="checkbox" name="topics" value="css" checked> CSS</label>
      <label><input type="checkbox" name="topics" value="jquery"> jQuery</label>
    </fieldset>
    <label><input id="agree" type="checkbox"> I agree to the workshop rules</label>
    <p><button id="send" type="submit" disabled>Register</button></p>
  </form>
  <p id="out" role="status">Tick "I agree" to enable the button.</p>

  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <script>
// Enable the button only when the box is ticked
$('#agree').on('change', (event) => {
  $('#send').prop('disabled', !$(event.currentTarget).prop('checked'));
});

$('#workshop').on('submit', (event) => {
  event.preventDefault();
  const topics = $('input[name="topics"]:checked').map((i, el) => el.value).get();

  $('#out').empty();   // clear, then add safe text and elements
  $('#out').append(
    'serialize(): ', $('<code>').text($('#workshop').serialize()), $('<br>'),
    'batch: ', $('<code>').text($('input[name="batch"]:checked').val()), $('<br>'),
    'topics array: ', $('<code>').text(JSON.stringify(topics))
  );
});

// For this demo: tick the box and submit once, so the result shows
$('#agree').prop('checked', true).trigger('change');
$('#workshop').trigger('submit');
  </script>
</body>
</html>

Watch out: A disabled submit button is fine for a short form like this, but do not rely on it alone. People cannot tell why a disabled button will not work, and screen readers may skip it. For longer forms, keep the button enabled and explain the problems after submit.

jQuery validation on submit

Listen for submit on the form, call event.preventDefault(), check each field and either show errors or send the data. You do not have to write every rule yourself. HTML attributes such as required, type="email" and pattern already describe the rules, and each input’s validity object tells you which rule failed: valueMissing, typeMismatch, patternMismatch and so on. Adding novalidate to the form turns off the browser’s own pop-up bubbles so that you can show friendlier messages in the page instead.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Inline validation with accessible errors</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    label { display: block; margin-top: 10px; font-weight: 600; }
    input, select { padding: 6px; }
    .error { color: #b91c1c; font-size: .9rem; margin: 2px 0 0; }
    [aria-invalid="true"] { border: 2px solid #b91c1c; }
    #done { color: #166534; }
  </style>
</head>
<body>
  <form id="enquiry" novalidate>
    <label for="name">Full name</label>
    <input id="name" name="name" type="text" required value="Meena" aria-describedby="name-error">
    <p id="name-error" class="error"></p>

    <label for="email">Email</label>
    <input id="email" name="email" type="email" required value="meena@" aria-describedby="email-error">
    <p id="email-error" class="error"></p>

    <label for="mobile">Mobile (10 digits)</label>
    <input id="mobile" name="mobile" type="tel" required pattern="[6-9][0-9]{9}" value="98765" aria-describedby="mobile-error">
    <p id="mobile-error" class="error"></p>

    <label for="city">City</label>
    <select id="city" name="city" required aria-describedby="city-error">
      <option value="">Choose a city</option>
      <option>Puducherry</option>
      <option>Chennai</option>
      <option>Madurai</option>
    </select>
    <p id="city-error" class="error"></p>

    <p><button type="submit">Send enquiry</button></p>
  </form>
  <p id="done" role="status"></p>

  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <script>
const messages = {
  name: { valueMissing: 'Please enter your name.' },
  email: { valueMissing: 'Please enter your email.', typeMismatch: 'Enter an email like [email protected].' },
  mobile: { valueMissing: 'Please enter your mobile number.', patternMismatch: 'Enter 10 digits starting with 6, 7, 8 or 9.' },
  city: { valueMissing: 'Please choose your city.' }
};

// Returns true if the field is valid, and shows or clears its message
function checkField(field) {
  const $field = $(field);
  const v = field.validity;                  // the browser's own checks
  const rules = messages[field.name] || {};
  let message = '';

  if (v.valueMissing) message = rules.valueMissing;
  else if (v.typeMismatch) message = rules.typeMismatch;
  else if (v.patternMismatch) message = rules.patternMismatch;
  else if (field.name === 'name' && $field.val().trim().length < 3) message = 'Name must have at least 3 letters.';

  $field.attr('aria-invalid', message ? 'true' : 'false');
  $('#' + field.id + '-error').text(message);
  return !message;
}

// Check a field when the user leaves it
$('#enquiry').find('input, select').on('blur', (event) => checkField(event.currentTarget));

$('#enquiry').on('submit', (event) => {
  event.preventDefault();
  const fields = $('#enquiry').find('input, select').get();
  const invalid = fields.filter((field) => !checkField(field));

  if (invalid.length) {
    $('#done').text(`Please fix ${invalid.length} field(s).`);
    invalid[0].focus();                      // take the user to the first problem
    return;
  }
  $('#done').text('Thanks! We will call you within one working day.');
});

$('#enquiry').trigger('submit');   // demo: show the errors for the sample values
  </script>
</body>
</html>

The demo starts with wrong sample values and submits once, so you can see the errors. Correct the fields and press “Send enquiry” again.

Making errors accessible

  • Put the message in the page, next to the field, in words that say how to fix it – not just “Invalid”.
  • Link the message to its field with aria-describedby, so screen readers read it when the field is focused.
  • Set aria-invalid="true" on fields with errors, and back to false when fixed.
  • Do not use colour alone; the text message carries the meaning.
  • After a failed submit, move focus to the first invalid field.

Run jQuery validation when a field loses focus (blur) rather than on every keystroke. Shouting “Invalid email” after the first letter is annoying; checking when the person moves on feels helpful. The post Web accessibility basics covers more of these rules.

Sending a valid form

Once every field passes, you can send the data with AJAX instead of a full page load. Two details make this feel solid. First, disable the submit button and change its label while the request is travelling, so an impatient double-click does not create two enquiries. Second, re-enable it in .always(), so the person can try again if the network fails. The server’s reply decides the final message: only say “Thanks” after the server confirms.

const $form = $('#enquiry');
const $btn = $form.find('button[type="submit"]');

$btn.prop('disabled', true).text('Sending…');

$.post('/enquiry.php', $form.serialize())        // same data as a normal submit
  .done(() => {
    $('#done').text('Thanks! We will call you within one working day.');
    $form.trigger('reset');                      // clear the fields
  })
  .fail(() => $('#done').text('Sorry, sending failed. Please try again.'))
  .always(() => $btn.prop('disabled', false).text('Send enquiry'));

You may also find the popular jQuery Validation plugin in older projects. It adds rules and messages through a .validate() method. It is worth recognising, but the approach above uses only the browser’s built-in rules plus a few lines of jQuery, with nothing extra to download.

Tip: Client-side jQuery validation is for convenience only. Anyone can switch off JavaScript or send requests directly, so the server must check everything again. See PHP Forms for the server side.

Further reading

Key takeaways

  • Read fields with .val(), checkboxes with .prop('checked'), radio groups with :checked, and whole forms with .serialize().
  • Validate in a submit handler with event.preventDefault().
  • Use HTML attributes (required, type, pattern) and each field's validity object instead of writing every rule.
  • Show messages in the page with aria-describedby and aria-invalid, and focus the first invalid field.
  • Always validate again on the server.

Quick quiz

Check what you learned – pick an answer.

1. How do you get the value of the chosen radio button in a group named size?

2. Which fields does .serialize() leave out?

3. Why add aria-describedby="email-error" to the email input?

Practice exercise

In the change-password form, check on submit that the new password has at least 8 characters and that both passwords match. Show the problem under the confirm field with aria-invalid set, or "Password changed." if everything is fine.

Show a hint

Read both values with .val(); compare them with !== and set the message with .text().

Show the solution
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Exercise: forms</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; line-height: 1.5; }
    label { display: block; margin-top: 10px; }
    .error { color: #b91c1c; font-size: .9rem; margin: 2px 0 0; }
    input[aria-invalid="true"] { border: 2px solid #b91c1c; }
  </style>
</head>
<body>
  <form id="pw-form" novalidate>
    <label for="pw">New password (at least 8 characters)</label>
    <input id="pw" name="pw" type="password" autocomplete="new-password" value="chennai2026">
    <label for="pw2">Confirm password</label>
    <input id="pw2" name="pw2" type="password" autocomplete="new-password" value="chennai2025" aria-describedby="pw2-error">
    <p id="pw2-error" class="error"></p>
    <button type="submit">Change password</button>
  </form>
  <p id="result" role="status"></p>

  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <script>
$('#pw-form').on('submit', (event) => {
  event.preventDefault();
  const pw = $('#pw').val();
  const pw2 = $('#pw2').val();
  let message = '';

  if (pw.length < 8) message = 'The new password needs at least 8 characters.';
  else if (pw !== pw2) message = 'The two passwords do not match.';

  $('#pw2').attr('aria-invalid', message ? 'true' : 'false');
  $('#pw2-error').text(message);
  $('#result').text(message ? '' : 'Password changed.');
  if (message) $('#pw2').trigger('focus');
});

$('#pw-form').trigger('submit');   // show the result for the sample values
  </script>
</body>
</html>

Progress is saved in this browser only.