# Checking a Form with JavaScript

Let the browser check a form with required, min and type, ask it in code with checkValidity(), add rules of your own with setCustomValidity(), and show each error next to its field where everyone, screen reader users included, can find it.

Journeyman · JavaScript · HTML · Updated November 1, 2026

## After this lesson you can

- Use the browser's built-in checks (required, min, type) and checkValidity() to find the fields that aren't filled in right
- Add a rule of your own with setCustomValidity() and show its message next to the field, tied to it with aria-describedby

## Key takeaways

- Attributes such as required, min and type="email" make the browser check a field, and it won't send the form until every check passes.
- checkValidity() answers true or false for one field or a whole form, and the field's validity says which rule failed.
- setCustomValidity("message") marks a field as wrong, in your words. Call setCustomValidity("") once it's fixed, or the field stays wrong.
- Put each error message in the page next to its field, and point the field's aria-describedby at it, so a screen reader reads it too.
- Checks in the page help people fill a form in. A server still checks again, because anyone can get around a page's JavaScript.

You press **Sign up** and the page says *Please fill out this field*, with an arrow pointing at the box you missed. That's a form checking itself, and it's work you'll do on every form you build. Without it, the Reading List fills up with books that have no title, and a pizza order arrives with no address. With it, people find out what to fix right away, next to the box, in plain words. This lesson shows the checks the browser does for you, how to ask about them in code, how to add rules of your own, and how to show a message that everyone can find.

## The browser's built-in checks

A post office won't take a parcel with no address on it: it checks before it accepts. Checking what someone typed before you accept it is called **validation**. When it happens in the browser, before anything is sent anywhere, it's **client-side validation** (the browser is the *client*). In your textbook it may be called *HTML5 form validation* or *constraint validation*, since each rule is a **constraint** on what a field accepts.

You switch the checks on with attributes in the HTML:

```html
<input id="title" name="title" type="text" required>
<input id="pages" name="pages" type="number" required min="1">
<input id="email" name="email" type="email">
```

- `required`: the field can't be left empty.
- `min="1"` (and `max`): a number box's lowest (and highest) value.
- `type="email"`: the text must look like an email address, with an `@` and something after it.

Press **Add book** with the title empty and the browser stops the form being sent, puts the cursor in the title box, and shows a small bubble: in Chrome, *Please fill out this field.* No JavaScript needed.

## Asking in code: checkValidity()

Your code can ask the same question. Every field has a `checkValidity()` method that answers `true` (it passes every check) or `false`. Its `validity` property says which check failed, and `validationMessage` holds the browser's words:

```javascript
const pagesInput = document.getElementById("pages");
pagesInput.value = "0";

console.log(pagesInput.checkValidity());
console.log(pagesInput.validity.rangeUnderflow);
console.log(pagesInput.validationMessage);
```

With `min="1"`, the Console shows `false`, `true`, and in Chrome *Value must be greater than or equal to 1.* The `validity` property holds a `true` or `false` for each rule:

| `validity` property | It's `true` when… |
|---|---|
| `valueMissing` | a `required` field is empty |
| `rangeUnderflow` | a number is below `min` |
| `rangeOverflow` | a number is above `max` |
| `typeMismatch` | the text doesn't fit the `type`, such as an email with no `@` |
| `customError` | your own rule failed (next section) |

A form has `checkValidity()` too, and it answers `true` only when every field in it passes: `form.checkValidity()`.

**Try it.** In your `reading-list` page, add `required` to the title box and `required min="1"` to the pages box. Press **Add book** with the title empty, then with 0 pages. Then type `document.getElementById("pages").validity` in the Console and open it up to see every rule.

## Your own rule: setCustomValidity()

The built-in checks miss things. Type three spaces in the title box and `required` is happy: a space counts as something. But a book called `"   "` isn't a book. For a rule like that, `setCustomValidity()` marks a field as wrong, with your message:

```javascript
const titleInput = document.getElementById("title");

titleInput.addEventListener("input", () => {
  if (titleInput.value.trim() === "") {
    titleInput.setCustomValidity("A title needs at least one letter.");
  } else {
    titleInput.setCustomValidity("");
  }
});
```

- `setCustomValidity("A title needs at least one letter.")` makes `checkValidity()` answer `false`, sets `validity.customError` to `true`, and the browser shows your words in its bubble.
- `setCustomValidity("")`, with an empty string, says "this field is fine now". **Forgetting it is the most common bug here:** the field stays wrong forever, however the visitor fixes it, and the form can never be sent.

The listener is for `input`, which *Form Events* showed fires with every change to the box, so the rule is checked again as the visitor types.

## A message everyone can find

The browser's bubble disappears after a few seconds, and looks different in every browser. A message that stays is better: like a sticky note on the exact box of a paper form that needs fixing. Put an empty paragraph right after the field, for your code to fill in:

```html
<form id="add-book" novalidate>
  <label for="title">Title</label>
  <input id="title" name="title" type="text" required aria-describedby="title-error">
  <p id="title-error" class="error"></p>
  <button type="submit">Add book</button>
</form>
```

- `aria-describedby="title-error"` ties the message to the field. **ARIA** (Accessible Rich Internet Applications) is a set of attributes that tell screen readers what a page can't show them; `aria-describedby` names the element that describes this field. When someone using a screen reader moves to the title box, it reads the label, then the message.
- `novalidate` on the form switches off the browser's bubbles, because your messages replace them. The checks still work: `checkValidity()` answers exactly as before.

An error shown only as a red border fails people who can't see red, so the message is always in words.

## Putting it together when the form is sent

The best moment to check everything is when the form is sent. Listen for `submit`, cancel the sending with `event.preventDefault()` as *Canceling Events* showed (the Reading List keeps its books in the page, so it never sends the form anywhere), then check:

```javascript
const form = document.getElementById("add-book");
const titleInput = document.getElementById("title");
const titleError = document.getElementById("title-error");

form.addEventListener("submit", (event) => {
  event.preventDefault();
  if (!titleInput.checkValidity()) {
    titleError.textContent = "Every book needs a title.";
    titleInput.setAttribute("aria-invalid", "true");
    titleInput.focus();
    return;
  }
  titleError.textContent = "";
  titleInput.removeAttribute("aria-invalid");
  console.log(`Added: ${titleInput.value.trim()}`);
});
```

When the title is missing, the message appears under the box, `aria-invalid="true"` tells a screen reader the field has a problem, and `focus()` puts the cursor in the box, ready to fix. When it's fixed, the message and the mark are cleared. With a title typed, the Console shows `Added: The Hobbit`.

One thing these checks are not: security. Anyone can switch off a page's JavaScript, or send data to a server without using your page at all. Checks in the page are there to help people; a site that stores the data checks it again on its server (**server-side validation**).

## Check yourself

2 questions to try on the lesson page.

## So what

A form that says exactly what's wrong, right where it's wrong, gets filled in faster and by more people, including the ones using a keyboard or a screen reader. The same three moves work on any form: let the browser check what it can, add your own rules, and put the message next to the field.

## Key terms

- [Client-Side Validation](https://www.ask-codey.com/glossary/client-side-validation)
- [Constraint validation](https://www.ask-codey.com/glossary/constraint-validation)
- [required](https://www.ask-codey.com/glossary/required)
- [Validity state](https://www.ask-codey.com/glossary/validity-state)
- [aria-describedby](https://www.ask-codey.com/glossary/aria-describedby)

## Cheat sheets

- [Checking a Form with JavaScript](https://www.ask-codey.com/cheat-sheets/javascript-forms-validation-cheat-sheet)

## Related

- [Reading What People Type](https://www.ask-codey.com/lessons/javascript-forms-reading-values)
- [Canceling Events](https://www.ask-codey.com/lessons/common-events-cancelling-events)

Source: https://www.ask-codey.com/lessons/javascript-forms-validation
