# HTML5 form validation without writing JavaScript

> A practical guide to HTML5 form validation using required, pattern, input types and length limits, with copy-ready examples and styling tips.

Developers

A practical guide to HTML5 form validation using required, pattern, input types and length limits, with copy-ready examples and styling tips.

By [Venkatesh](https://www.linkedin.com/in/ageven/) 12 November 2026 · 6 min read

![Cover image: HTML5 form validation without writing JavaScript, a FormDoc guide](https://www.formdoc.dev/images/blog/html5-form-validation.png)

HTML5 form validation lets the browser check a form before it’s sent, using plain attributes like `required`, `type="email"`, `pattern` and `maxlength`. If a field fails, the browser stops the submission and shows a message next to the field. You don’t need any JavaScript, and it works in every modern browser.

It isn’t a security feature (anyone can bypass it), but it’s the quickest way to catch honest mistakes like an empty message box or a typo in an email address.

Built-in validation runs instantly, in the visitor’s own language, with messages screen readers already know how to announce. It costs you a few attributes. Compared with writing your own checks in JavaScript, that’s a very good deal.

It also fits nicely with a form backend. When the browser has already checked the basics, fewer broken submissions reach your inbox, and visitors fix problems on the spot instead of waiting for an error page.

## The `required` attribute

The simplest rule: the field can’t be empty.

```html
<label for="message">Message</label>
<textarea id="message" name="message" required></textarea>
```

It works on text inputs, textareas, selects, checkboxes and radio buttons. For a group of radio buttons, putting `required` on one of them makes the whole group required.

Use it only on fields you truly need. A form where everything is required feels like an interrogation, and visitors may give up rather than fill it all in.

## Input types do half the work

Choosing the right `type` gives you validation and a better keyboard on phones, all at once:

| Input type | What the browser checks | Phone keyboard |
| --- | --- | --- |
| `email` | Looks like an email address (has an @ and a domain part) | Email layout with @ |
| `url` | Looks like a full URL with a scheme | URL layout |
| `tel` | Nothing by default (phone formats vary too much) | Number pad |
| `number` | A number, within `min` and `max`, matching `step` | Numeric |
| `date` | A valid date, within `min` and `max` | Date picker |
| `text` | Nothing unless you add `pattern` or length limits | Standard |

Notice that `tel` doesn’t validate anything. That’s deliberate: phone numbers come in many formats, and rejecting a perfectly good number is worse than accepting an odd one. If you want a rule, add a gentle `pattern`, but think twice first.

## Length limits: `minlength` and `maxlength`

These limit how many characters a field accepts:

```html
<label for="name">Your name</label>
<input id="name" name="name" type="text" minlength="2" maxlength="100" required>
```

`maxlength` stops typing at the limit, so visitors can’t overshoot. `minlength` shows a message if the value is too short, but only once the visitor has typed something; an empty field still needs `required`.

A sensible `maxlength` on your message box is a good habit. FormDoc accepts up to 10,000 characters per field and 64 KB of text per submission, so `maxlength="10000"` on a textarea means visitors find out about the limit while typing, not after pressing Send.

## Custom rules with `pattern`

`pattern` takes a regular expression that the whole value must match. Pair it with a `title` that explains the rule in plain words, because browsers often show it in the error message:

```html
<label for="pincode">PIN code</label>
<input id="pincode" name="pincode" type="text"
       inputmode="numeric" pattern="[0-9]{6}"
       title="Six digits, for example 560001" required>
```

A few tips for patterns:

-   **The pattern must match the entire value.** You don’t need `^` and `$`; the browser adds them for you.
-   **Keep it forgiving.** Allow spaces where people naturally type them, or strip them on the server.
-   **Explain the format in the label or hint text too.** Don’t make visitors discover the rule by failing it.
-   **Use `inputmode`** to bring up the right keyboard without changing the input type.

## Styling valid and invalid fields

CSS can react to validation state. The older `:invalid` selector matches as soon as the page loads, which means empty required fields look like errors before anyone has touched them. The newer `:user-invalid` selector only matches after the visitor has interacted with the field, which feels much kinder:

```css
input:user-invalid,
textarea:user-invalid {
  border-color: #b42318;
}
```

Never rely on colour alone to show an error. Keep the browser’s message, or add a short text hint, so people who can’t see the colour still know what went wrong.

## When you need a little JavaScript

Built-in validation covers most forms. A few cases need more:

1.  **Comparing two fields**, such as “confirm email”. Use `setCustomValidity()` to set a message on the second field when they don’t match, and clear it with an empty string when they do.
2.  **Custom error display.** Add `novalidate` to the form to switch off the browser’s bubbles, then use the Constraint Validation API (`checkValidity()`, `validity`) to show your own messages.
3.  **Sending without a page change.** If you submit with fetch, call `form.reportValidity()` first so the browser still checks everything.

If your script listens for the form’s `submit` event, the browser has already run its checks, because that event only fires once the form is valid. Your `required` and `pattern` rules keep working. The post on [submitting a form without page reload](https://www.formdoc.dev/blog/submit-form-without-page-reload/) covers that setup.

## HTML5 form validation is not enough on its own

Browser checks are a convenience for honest visitors. Bots and scripts post straight to your endpoint and skip them entirely. So the server must enforce its own limits.

FormDoc does this for you on every submission: it rejects payloads over its size limits, checks captcha tokens if you’ve turned a captcha on, and runs spam scoring before anything reaches your inbox. If you send submissions with JavaScript and `Accept: application/json`, errors come back as a code and a message you can show; the [API reference](https://www.formdoc.dev/docs/api-reference/) lists them.

## Common questions

### Can I change the browser’s default validation messages?

Yes, with `setCustomValidity()` in JavaScript. Set the message when the field is invalid and reset it to an empty string when it’s valid, or the field will stay invalid. For most forms, the default messages are clear and already translated, so only customise when you need to.

### Does `type="email"` check that the address really exists?

No. It only checks that the text looks like an email address. Whether the mailbox exists can’t be known until you send a message to it.

### Is HTML5 form validation accessible?

Mostly, yes. Browsers announce built-in messages to screen readers and move focus to the first invalid field. Make sure every input has a proper `<label>`, and add visible hint text for any format rules.

## Put it into practice

If you’re still deciding which `name`, `action` and `method` to use, read [HTML form action and method explained](https://www.formdoc.dev/blog/html-form-attributes-explained/). Then take one of the [form templates](https://www.formdoc.dev/templates/), add the attributes above, and connect it with a free key from [app.formdoc.dev/new](https://app.formdoc.dev/new).

Written by**[Venkatesh](https://www.linkedin.com/in/ageven/)**
