cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Form validation

Validate browser form fields with required, type, pattern, validity, setCustomValidity, custom messages, and accessible error UI while still trusting the server for final checks.

By the end, you can
  • 01
    Use built-in constraintsCombine required, input types, pattern, min, max, step, and length attributes.
  • 02
    Read and change validityInspect ValidityState, compare checkValidity() with reportValidity(), and clear custom errors.
  • 03
    Design humane errorsBuild custom messages, summaries, focus movement, aria-invalid, and live regions.

The bouncer at the form door

Form validation is the set of checks that decide whether a form value is ready to accept. A signup form might require an email, a booking form might require a date after today, and a coupon box might accept only a code shaped like SAVE20.

Browsers have a built-in system for many of these checks: the Constraint Validation API. You describe constraints with HTML attributes, then JavaScript reads properties such as input.validity, calls methods such as checkValidity(), and adds special rules with setCustomValidity().

Real-life analogyValidation is a nightclub bouncer with a checklist

The bouncer does not just say “no”. They look down a checklist: ticket present, ticket format right, age in range. The browser does the same for a form control. Each failed rule gets its own flag, which makes debugging much easier than one mysterious invalid state.

In real life: You must have a ticket
In JavaScript: required: the field cannot be empty
In real life: The ticket must look like tonight's format
In JavaScript: pattern and input type checks
In real life: Age limits at the door
In JavaScript: min, max, and step for numbers and dates
In real life: A tick or cross per rule
In JavaScript: validity: booleans such as valueMissing and typeMismatch

Where the analogy stops: A real bouncer can judge context and stop attackers. Browser validation can be bypassed, so your server still has to validate every important value.

Real-life analogyClient-side validation is a receptionist, not a security guard

Client-side validation is kind. It saves people a round trip and helps them fix small mistakes while the field is still in front of them. It is not proof that the data is safe. Anyone can send a request without using your page at all.

In real life: Receptionist points out a missing signature
In JavaScript: The browser warns about obvious mistakes immediately
In real life: Manager adds a rule for tonight
In JavaScript: setCustomValidity('Passwords must match')
In real life: Security verifies the ID later
In JavaScript: The server repeats trusted validation

Where the analogy stops: The receptionist improves the visit, but cannot be the final authority. Never store, charge, or trust data only because the browser accepted it.

required, pattern & input types

STEP THROUGH

Start with HTML. The browser already knows how to validate many common rules, and those rules also help mobile keyboards, password managers, and assistive technology understand the field.

  • required means the value cannot be empty. An empty required control has validity.valueMissing.
  • type="email", type="url", type="number", and date-like types add format or parsing rules. A bad email or URL gives typeMismatch.
  • pattern tests a regular-expression-like pattern against the whole value. It is ignored when the field is empty unless required also fails. The Regular expressions lesson comes later; for now, read [A-Z]3 as “three uppercase letters.”
  • min, max, and step validate ranges for numbers and dates. minlength and maxlength validate text length; browsers generally set tooShort after the user edits the value.

A control can also opt out of validation. input.willValidate is false for controls such as disabled inputs, readonly text-like controls, and type="hidden" inputs. That is the browser saying “this control is not standing in the validation line.”

Step through a constraint checklist
Step 0 of 5Ready
Your turn: follow the blue line

Pick a scenario, predict the flags, then step through the checklist.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const validity = check(field);console.log(validity.valueMissing);console.log(validity.patternMismatch);console.log(validity.valid);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose a field state to inspect

Switching the scenario starts a fresh replay with the same question: which flags become true?

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Notice the empty optional pattern case: the pattern flag stays false. If empty should be a problem, add required. If the field is optional, let people leave it blank without seeing a format error.

Read the validity checklist

INTERACTIVE

Every form control that participates in validation exposes validity, a ValidityState object. It has one boolean for each kind of failure and a final valid boolean. The final value is true only when every failure flag is false.

The dashboard below reads the browser’s real flags after the component mounts and whenever you type. It also shows validationMessage. That message is generated by the browser, so its exact words can vary by browser and language. Product copy should usually come from your own message map.

Validity dashboard
Constraint attributesPop out in the code editor (opens in a new tab)HTML
<form>  <input name="name" required>  <input name="email" type="email" required>  <input name="site" type="url">  <input name="tickets" type="number" min="1" max="10" step="2">  <input name="code" minlength="3" maxlength="8" pattern="[A-Z]{3}[0-9]{2}">  <input name="day" type="date" min="2026-01-01"></form>
Live form0 valid
Try it yourself
Type in the fields; the checklist updates on input.

The table reads each control's real validity object and browser validationMessage after the component mounts or the user edits a field. The message text can differ by browser and language.

A real form beside a real ValidityState dashboard. No validation messages are computed during server rendering.

Try these tiny investigations: leave Name empty to see valueMissing, type abc in Email to see typeMismatch, type 7 in Tickets to see stepMismatch, and type ab in Code after editing it to see the length rule.

Which validity flag?
  • An empty required field
  • abc in type=email
  • 7 in min=0 step=5
  • A password confirmation message from setCustomValidity
Try it yourself
0 of 4 correct

Sort each situation by the flag it turns on.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Length and pattern flags
  • 12 in a text field with minlength=3
  • abcdef in a field with maxlength=4
  • ab12 in a field with pattern="[A-Z]{2}[0-9]{2}"
Try it yourself
0 of 3 correct

Now sort text-specific failures by the flag they turn on.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

validity & setCustomValidity

INTERACTIVE

Built-in attributes cover individual fields well, but real products often have rules that compare fields: passwords must match, an end date must come after a start date, or a username cannot be on a reserved list. That is where setCustomValidity(message) enters.

Any non-empty message marks that one control invalid and turns on validity.customError. Calling setCustomValidity("") clears the custom error. The empty string is not a decoration; it is the off switch.

setCustomValidity lab
Password match rulePop out in the code editor (opens in a new tab)JavaScript
confirm.addEventListener("input", () => {  const match = password.value === confirm.value;  confirm.setCustomValidity(match ? "" : "Passwords must match");}); form.checkValidity();  // boolean, fires invalidform.reportValidity(); // boolean, asks browser to show UI
Try itinvalid events: 0
Try it yourself

Type matching passwords, then try the buttons.

A non-empty custom validity message keeps the confirmation field invalid until the code sets setCustomValidity("").
Validation methods that sound similar but do different jobs
MethodReturnsMain job
input.checkValidity() / form.checkValidity()BooleanRuns validation and fires invalid on invalid controls without showing native UI.
input.reportValidity() / form.reportValidity()BooleanRuns validation and asks the browser to report problems to the user.
input.setCustomValidity(message)undefinedSets or clears one custom error. Any non-empty message means invalid.

The invalid event fires on invalid controls during these checks. It does not bubble like many events, so listen in the capture phase on the form if you want one counter or logger.

Custom error messages

INTERACTIVE

Browser bubbles are useful for quick prototypes, but production interfaces often need consistent wording, summaries, translations, and layout. Add novalidate to the form to turn off automatic bubbles, then call checkValidity() yourself and render your own messages.

A good message tells the user how to fix the field: Use a real email address, like ada@example.com is better than Invalid input. Use the same validity flags, but translate them into friendly instructions.

Custom error UI with novalidate
Custom errors patternPop out in the code editor (opens in a new tab)HTML + JavaScript
<form novalidate>  <input id="email" type="email" required aria-describedby="email-error">  <p id="email-error"></p>  <button>Join</button></form> form.addEventListener("submit", (event) => {  event.preventDefault();  if (!form.checkValidity()) showFriendlyErrors();});
Friendly messages0 errors

Try it yourself

Submit to render custom messages instead of browser bubbles.

The form has novalidate, so the submit handler runs and renders friendly messages from the same validity flags.

After the first failed submit, update errors on input or blur. Before the first submit, many teams avoid showing red errors while the person is still typing. The newer CSS selector :user-invalid follows that idea in supporting browsers; :invalid can match before the user touches the field.

Accessible errors

INTERACTIVE

An error is not accessible just because it is red. People using a keyboard, screen reader, zoom, or voice control need programmatic relationships between the field, the message, and the summary.

  • Set aria-invalid="true" on fields that currently have errors.
  • Use aria-describedby to point to the visible error text.
  • Put a summary near the top with links to invalid fields.
  • Move focus to the first invalid field, or the summary, after submit.
  • Use a polite live region so new messages can be announced.
Accessible error wiring
Accessible error attributesPop out in the code editor (opens in a new tab)HTML
<div role="alert" aria-live="polite" id="error-summary"></div><input id="email" required aria-invalid="true" aria-describedby="email-error"><p id="email-error">Enter your email address.</p>
Rendered patternerror on

Enter your email address.

Try it yourself

When the error is shown, aria-invalid tells assistive tech the field is invalid, aria-describedby connects the field to the message, and the summary uses a live region.

This is the wiring pattern to pair with custom validation messages and focus movement.

The form events lesson introduced focus movement and submit handling. Validation combines those skills: catch the failed submit, show a summary, focus a useful target, and keep the messages connected while the user fixes each field.

Where you will use this

In a signup form, you might use type="email" required for the email, minlength="8" for a password, setCustomValidity() for password confirmation, and a custom summary on submit. In checkout, range and step rules can validate quantities. In settings, custom validity can block a username that is reserved by your app.

Professional habit

Prefer built-in constraints first, add custom validity only for rules the browser cannot express, and always validate again on the server. The browser improves the experience; the server protects the data.

Once a form passes validation, the next lesson, FormData, shows how to collect the values with new FormData(form) and prepare them for sending.

Common misconceptions

  • “The browser accepted it, so it is safe.” No. The server must validate every trusted rule.
  • “pattern makes a field required.” No. Empty optional fields skip pattern.
  • “A custom error clears itself.” No. You must call setCustomValidity("").
  • “validationMessage is my product copy.” It is browser and locale text. Map flags to your own copy when wording matters.
  • “A red border is enough.” Accessible errors need text, relationships, summaries, and focus.
Tempting shortcut versus reliable validation habit
ShortcutBetter habit
Only use JavaScript if checksUse HTML constraints so the browser, keyboard, and accessibility tree understand the rule.
Show every error immediatelyWait until submit or user interaction, then update clearly.
Trust client-side validationValidate on the server before saving, charging, or authenticating.
Write one generic errorMap the specific validity flag to a helpful next action.

Practice exercises

4 EXERCISES
Exercise 1 · Warm-upName the empty required flag

A required text input is empty. Type the validity flag that becomes true.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log("valueMissing");

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite the password match rule

    Read the solution pattern, then answer: what exact string clears a custom validity error?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const password = form.elements.password;
    const confirm = form.elements.confirm;
    
    function validatePasswords() {
      const message = password.value === confirm.value ? "" : "Passwords must match";
      confirm.setCustomValidity(message);
    }
    
    password.addEventListener("input", validatePasswords);
    confirm.addEventListener("input", validatePasswords);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeChoose the reporting method

      You want the browser to show its built-in validation UI for a field. Which method do you call?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeDesign an accessible failed submit

        After a submit fails, which invalid field should receive focus first?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function showError(input, message) {
          const error = document.querySelector("#" + input.id + "-error");
          input.setAttribute("aria-invalid", message ? "true" : "false");
          input.setAttribute("aria-describedby", error.id);
          error.textContent = message;
        }

        Answer, then press Check. Spacing and letter case don’t matter.

          Check your understanding

          7 QUESTIONS
          Lesson quiz · 7 questionsScore: first tries count
          1. Question 1 of 7What happens when an optional text field has pattern="[A-Z]{3}" and its value is empty?

            Choose an answer to see the explanation.

          2. Question 2 of 7What does the custom validity snippet print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const message = "Passwords must match";
            console.log(message === "" ? "valid" : "customError");

            Choose an answer to see the explanation.

          3. Question 3 of 7Which method returns a boolean and fires invalid on invalid controls, but does not show the browser bubble UI?

            Choose an answer to see the explanation.

          4. Question 4 of 7What does the first-flag helper print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            function firstFlag(flags) {
              return flags.valueMissing ? "valueMissing" : flags.valid ? "valid" : "other";
            }
            console.log(firstFlag({ valueMissing: true, valid: false }));

            Choose an answer to see the explanation.

          5. Question 5 of 7Which controls usually have willValidate as false?

            Choose an answer to see the explanation.

          6. Question 6 of 7Why use novalidate when building custom error UI?

            Choose an answer to see the explanation.

          7. Question 7 of 7What does the client-and-server snippet print?

            Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
            const serverMustCheck = true;
            const browserHelpful = true;
            console.log(browserHelpful && serverMustCheck ? "both" : "browser only");

            Choose an answer to see the explanation.

          Key takeaways

          • HTML constraint attributes give you powerful validation before you write custom JavaScript.
          • validity is a checklist of booleans; valid is true only when no rule fails.
          • setCustomValidity("message") creates customError; setCustomValidity("") clears it.
          • checkValidity() tests; reportValidity() tests and asks the browser to report.
          • Custom UI must be accessible, and the server must validate again.

          Form validation is the browser and app checking form values against rules, helping users fix mistakes before trusted server validation runs.

          Up next: FormData.

          CompleteFrontend Clear concepts. Working examples.