Native form validation is easy
Tags: validation, forms, webdev, dom
Published: 2023-11-29 01:44:14

**You can validate your forms without JavaScript.**

_Less than 6 minutes, 1470 words, 4th grade_

It is easy to **create web forms that validate themselves without JavaScript.** Why so many developers ignore this native capability is an open question.

I guess that when all you have is React or similar, then everything looks like a React component.

But what happens when the user disables JavaScript (or it is otherwise unavailable)? OK, you are rendering your React components on the server. Nice. But what happens to your client-side validation?

But before we get into this, a brief digression. Letʼs take another look at our previous article, [Progressive enhancement](https://dev.to/craft-code/progressive-enhancement-31ii).


## The `requestAnimationFrame` option

After I published that article, I got a suggestion from a good friend. He recommended that I animate the accordion elements with `requestAnimationFrame`. So I tried it:

```js
function toggleAccordion (event) {
  event.preventDefault()
  
  const summary = event.target
  const accordion = summary.closest("details")
  const content =
    accordion.querySelector(".xx-accordion-content")
  const openHeight = summary.xxOpenHeight

  if (accordion.open) {
    function shutAccordion (
      height,
      stopHeight = 0,
      decrement = 20
    ) {
      return function () {
        content.style.maxHeight = `${height}px`

        if (height <= stopHeight) {
          accordion.open = false
          
          return
        }

        requestAnimationFrame(
          shutAccordion(
            height - decrement,
            stopHeight,
            decrement
          )
        )
      }
    }

    shutAccordion(openHeight)()

    return
  }
  function openAccordion (
    height,
    stopHeight,
    increment = 10
  ) {
    return function () {
      content.style.maxHeight = `${height}px`

      if (height >= stopHeight) {
        return
      }

      requestAnimationFrame(
        openAccordion(
          height + increment,
          stopHeight,
          increment
        )
      )
    }
  }

  accordion.open = true

  openAccordion(0, openHeight)()
}
```

I wonʼt go into a lot of detail here as it is pretty self-explanatory. We create open and close **outer functions** that return an **inner closure.** `requestAnimationFrame` calls that closure recursively. In this way, we can **increment or decrement the height on each frame** immutably.

Is this a potential performance bottleneck, creating all those closures? Maybe. But itʼs the simplest way to do it, and we donʼt [prematurely optimize](https://craft-code.dev/glossary/p#premature-optimization) here at Craft Code. If it turns out to be a bottleneck, weʼll switch to a loop and a mutable variable.

Betcha it works just fine.

**But which is better?** `setTimeout`? Or `requestAnimationFrame`?

Itʼs a pretty close call. `setTimeout` is older and will work in browsers such as Internet Explorer pre-v10. Of course we can polyfill that.

An argument for `requestAnimationFrame` is that it may be smoother. Especially if there are many simultaneous animations on the page. It also may be more performant. Donʼt need to support very old browsers? Then `requestAnimationFrame` is probably the better way to go.

You should ask, “Where in the world are my users? What browsers are they using?” That will help you to determine which option is best. But then we should always start with that question, right?

Try it on our [example animation frame accordion.](http://localhost:3002/essays/code/native-form-validation-is-easy/example/animation-frame)

## About those forms

Letʼs get to our forms. A few simple recommendations to start.

- Donʼt use placeholders. Just donʼt. **Use `<label>` instead.** Set the `for` attribute to the `id` of the input labelled.
- Group related controls in `<fieldset>` elements. Use the `<legend>` element to label the group. Fieldsets are easily styled with CSS these days.
- Ensure that your form elements are keyboard navigable. And in the same order that they appear visually. **Donʼt confuse your users.**
- You can best determine other considerations, such as where to put buttons or which buttons to use, with user testing. **There is no one right way.**

With those caveats in mind, let us begin with a simple form. Here is one with which we are all familiar … but not for much longer, we hope.

```html
<form
  action=""
  class="xx-form"
  method="GET"
  name="simple-form"
>
  <fieldset class="xx-fieldset">
    <legend>Please sign in</legend>

    <div class="xx-form-field">
      <label
        class="xx-field-label"
        for="email"
        id="xx-email-label"
      >
        Email address
      </label>
      <br>
      <div class="xx-field-help" id="xx-email-help">
        The email address with which you signed up.
      </div>
      <input
        aria-labelledby="xx-email-help xx-email-label"
        class="xx-field-input xx-email-field"
        id="email"
        name="email"
        required
        size="36"
        type="email"
      >
    </div>

    <div class="xx-form-field">
      <label
        class="xx-field-label"
        for="password"
        id="xx-password-label"
      >
        Password
      </label>
      <br>
      <div class="xx-field-help" id="xx-password-help">
        Four or more space-separated words of 4+ characters.
      </div>
      <input
        aria-labelledby="xx-password-help xx-password-label"
        class="xx-field-input xx-password-field"
        id="password"
        name="password"
        pattern="[a-zA-Z]{4,}( [a-zA-Z]{4,}){3,}"
        required
        size="36"
        type="password"
      >
    </div>
  </fieldset>
  
  <button
    class="xx-submit-button"
    type="submit"
  >
    Sign in
  </button>
</form>
```

As in previous examples, we use CSS class names on all our elements. The `xx-` is a **namespace**, i.e., `cc-` for Craft Code. This allows us to select our elements in our CSS and avoids collisions.

We like grouping controls in fieldsets, and using the `legend` for the title of our form. We group our labels and inputs in `<div>` elements with the class name, `xx-form-field`. This permits us to style them as a group.

Our view is that best practice is to **put the label above the input.** To make this work even when the user disables CSS, we use a `<br>` element. Note that we **tie our labels to their inputs** with the `for` attribute.

Whenever possible, we choose our HTML elements to take advantage of browser features. We chose an `<input>` of type `email` rather than type `text`. Because of this, the browser will validate the value of the input on submit.

If the value is **not a potentially valid email address**, then submission fails.

We also set the `required` attribute on the input. The form will **require** a valid email address before we can submit it.

One down.

Then for the password field, we choose the `password` type, which **masks the characters** as we type them. We also set the `required` attribute so the user must provide a password. We could use `minlength` to set a minimum length for the password, but we have a better idea.

As [this brilliant xkcd comic](https://xkcd.com/936/) makes clear, a better approach to passwords is to use four random words. To this end, we have used the `pattern` attribute on the password input. It requires the password to be four or more space-separated words of four or more characters each.

The pattern: `[a-zA-Z]{4,}( [a-zA-Z]{4,}){3,}`.

Of course, we make this requirement clear with a help message above the input. There a screen reader will announce it _before_ entering the field. And to be extra certain, we associate it with the input via the `aria-labelledby` and `id` attributes as shown.

Hereʼs what that looks like:

![Submitting the form with a bad email address raises the message, “Please include an '@' in the email address.”](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r6oe9pajzggp76z25nal.png)

Here are the possibilities:

- **Empty input** (required): “Please fill out this field.”
- **bob@:** “Please enter a part following '@'. 'bob@' is incomplete.”
- **@dobbs:** “Please enter a part followed by '@'. '@dobbs' is incomplete.”
- **bob.dobbs:** “Please include an '@' in the email address. 'bob.dobbs' is missing an '@'.” (As above.)

Here is what happens if the password doesnʼt match our pattern:

![Submitting the form with a bad password raises the message, “Please match the requested format.”](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pqhk9z9npirfby9hztyq.png)

Here are the possibilities:

- **Empty input** (required): “Please fill out this field.”
- **Bob is yer uncle:** “Please match the requested format.”

**Be sure that you explain what the “requested format” is!** Donʼt make your users guess!

Try “correct horse battery staple”. Does it work? xkcd would be proud.

Give this [simple form](http://localhost:3002/essays/code/native-form-validation-is-easy/example/simple-form) a try.

## What else can we validate?

Different types of input provide different validations. Here are some examples:

- **Pattern mismatch:** You can use the `pattern` attribute with other input types as well. These include: `email`, `search`, `tel`, `text`, and `url`.
- **Range overflow/underflow:** You can set `max` and `min` values on types `date`, `datetime-local`, `month`, `number`, `range`, `time`, and `week`.
- **Step mismatch:** You can set the `step` value (a number) on types `date`, `datetime-local`, `month`, `number`, `range`, `time`, and `week`. Sheesh! Who knew there were so many input types? <abbr title="Mozilla Developer Network">MDN</abbr> provides a [handy list of default values.](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/step)
- **Too long/short:** You can set a `maxlength` and/or `minlength` (as a number of characters) on types `email`, `password`, `search`, `tel`, `text`, and `url`.

Donʼt try to memorize these. Remember: code (and learn) **just in time.** There is no point in wasting time and effort that you may never need.

Instead, first design your form. Then determine what needs validation. Finally, refer to [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState) or equivalent to see what fits your needs.

For example, you might create an **integer input** like this:

```html
<input
  id="iq"
  max="210"
  min="0"
  name="iq"
  required
  step="1"
  type="number"
>
```

This accepts only **positive integers** between 0 and 210 (the highest IQ on record). The `step` value of `1` does not prevent entering decimals, such as <kbd>100.1</kbd>. But try submitting that. Youʼll get a warning:

“Please enter a valid value. The two nearest valid values are 100 and 101.”

You can try it on our [example form](https://craft-code.dev/essays/code/native-form-validation-is-easy/example/test-inputs). What happens if you enter <kbd>-50</kbd>? What about <kbd>300</kbd>?

You could also do this with an input of type `text` by setting the `pattern` attribute to `[0-9]*`. Or use `([0-9]|[1-9][0-9]*)`? if you want to disallow starting zeros. But either way you lose the semantic value of the `number` type.

Our recommendation: stick with the `number` input.

The key takeaway here is this: **good enough is, by definition, good enough.**

Too often, designers and devs create **bloated, ugly, brittle, incomprehensible code.** We do it because we want to tweak our interface in some minor way, but plain HTML doesnʼt make it easy. So we hack the heck out of it.

The temptation to throw out all the benefits of semantic, accessible, browser-native code just to make it a bit slicker can be overwhelming. But **resist, resist, resist!**

This isnʼt about UX. Your users donʼt care about your flashy interface, no matter what they tell you when you ask. Thatʼs your ego talking.

**Users care about usability, findability, comprehensibility, accessibility.**

Can they find what they are looking for? Can they understand it when they find it? Can they make your site do what they want it to do?

And we can manage all that with an elegant design without having to **hack** the code. Simple and beautiful. And more stable, less likely to be buggy, and be easier to code and refactor, too.

And if they never see your flashy, edgy new design, they will never miss it.