---
area: "Learn"
parent: "Forms chapter"
url: "https://unpoly.com/learn/validation"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [Forms chapter](https://unpoly.com/learn/forms.md)</nav>

# Validating forms

Unpoly shows validation errors from your server, both after a form was submitted and while the user is still filling in fields. The error messages are rendered by your backend, with the same templates that render the form.

<nav aria-label="Contents">

#### Contents

- [Validating after submission](#validating-after-submission)
- [Validating after changing a field](#validating-after-changing-a-field)
- [Validating while typing](#validating-while-typing)
- [Validating from JavaScript](#script)

</nav>

## Validating after submission {#validating-after-submission}

When a server-side app cannot save a form submission due to invalid user input, it usually re-renders the form with validation errors. This pattern also works for forms that are [submitted through Unpoly](https://unpoly.com/learn/submitting-forms.md).

Let's look at a registration form that asks for an e-mail address and a password:

```html
<form method="post" action="/users" up-submit>

  <fieldset>
    <label for="email">E-mail</label>
    <input type="text" id="email" name="email">
  </fieldset>

  <fieldset>
    <label for="password">Password</label>
    <input type="password" id="password" name="password">
  </fieldset>

  <button type="submit">Register</button>

</form>
```

We have some constraints that we want to validate when the form is submitted:

| Field | Validation |
| --- | --- |
| `email` | must be formatted correctly |
| `email` | must not be taken by another user |
| `password` | must be longer than 8 characters |

The backend code handling `POST /users` needs to handle two cases:

1. The form was submitted with valid data. We create a new account and sign in the user.
2. The form submission failed due to an invalid e-mail or password. We re-render the form with error messages.

In the second case, we render the form again with the submitted values and a message below each invalid field:

```html
<form method="post" action="/users" up-submit>

  <fieldset>
    <label for="email">E-mail</label>
    <input type="text" id="email" name="email" value="foo@bar.com">
    <div class="error">E-mail has already been taken!</div>
  </fieldset>

  <fieldset>
    <label for="password">Password</label>
    <input type="password" id="password" name="password" value="secret">
    <div class="error">Password is too short!</div>
  </fieldset>

  <button type="submit">Register</button>

</form>
```

### Signaling a failed submission {#signaling-a-failed-submission}

For Unpoly to detect the failed submission, the backend must respond with an HTTP status code outside the 2xx range. We recommend [HTTP 422](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) (Unprocessable Content).

In a Ruby on Rails app this would look like this:

```ruby
class UsersController < ApplicationController

  def create
    user_params = params.permit(:email, :password)
    @user = User.new(user_params)
    if @user.save
      sign_in @user
    else
      # Signal a failed form submission with an HTTP 422 status
      render 'form', status: :unprocessable_entity
    end
  end

end
```

The examples use Ruby on Rails, but work similarly with any other backend.

If your server-side code cannot communicate status codes like that, you may [customize Unpoly's failure detection](https://unpoly.com/learn/error-responses.md#customizing-failure-detection).

### Changing how validation errors are rendered {#changing-how-validation-errors-are-rendered}

When Unpoly detects a failed submission, it ignores the form's [target](https://unpoly.com/learn/targeting-fragments.md) and updates the `<form>` element instead. This way validation errors always appear next to the user's input:

```html
<form method="post" action="/comments" up-submit up-target="#comments">
  <!-- ❌ Error responses will appear here -->
</form>

<div id="comments">
  <!-- ✔️ Successful responses will appear here -->
</div>
```

The form's values and messages all come from your server-rendered HTML. Unpoly only decides which fragment to show them in.

To render an error response somewhere else, set an [`[up-fail-target]`](https://unpoly.com/api/up-submit.md#up-fail-target) attribute on the `<form>` element:

```html
<form method="post" action="/comments" up-submit up-target="#comments" up-fail-target="#errors">
  ...
</form>

<div id="comments">
  <!-- ✔️ Successful responses will appear here -->
</div>

<div id="errors">
  <!-- ❌ Error responses will appear here -->
</div>
```

Most render options have such a `fail`-prefixed variant. See [rendering error responses differently](https://unpoly.com/learn/error-responses.md#fail-options) for details.

### Focusing the first invalid field {#focusing-invalid-fields}

In a long form, the fields with errors may be outside the visible screen area after a failed submission. To focus the first invalid field and scroll it into view, set [`{ failFocus }`](https://unpoly.com/api/up.render.md#options.failFocus) and [`{ failScroll }`](https://unpoly.com/api/up.render.md#options.failScroll) options for every form submission:

```js
up.on('up:form:submit', function(event) {
  event.renderOptions.failFocus ??= '[aria-invalid="true"], auto'
  event.renderOptions.failScroll ??= '[aria-invalid="true"], auto'
})
```

This expects your server to render invalid fields with an `[aria-invalid="true"]` attribute, which also tells assistive technology about the error:

```html
<input type="text" id="email" name="email" value="foo@bar.com" aria-invalid="true">
```

If your app marks invalid fields differently, like with an `.is-invalid` class, use that selector instead.

When no field matches, Unpoly falls back to its default [focus](https://unpoly.com/learn/focus.md#auto) and [scroll](https://unpoly.com/learn/scrolling.md#auto) behavior, because of the `auto` at the end. The `??=` operator keeps an `[up-fail-focus]` or `[up-fail-scroll]` attribute that a form sets itself.

### HTML5 validations {#html5-validations}

HTML5 added a number of validations through attributes like [`[required]`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required) or [`[pattern]`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern). These validations are checked by the browser when the form is submitted.

You may also use HTML5 validation attributes for forms that are [submitted through Unpoly](https://unpoly.com/learn/submitting-forms.md). A failed HTML5 validation prevents the form from being submitted.

> #### Caution
>
> Client-side validations are no substitute for server-side checks. A malicious user can always alter the network request.

## Validating after changing a field {#validating-after-changing-a-field}

With the [`[up-validate]`](https://unpoly.com/api/up-validate.md) attribute, a field is validated on the server _as soon as the user leaves it_. This gives the user quick feedback whether their input is valid, without the need to scroll for error messages or to backtrack to fields completed earlier.

To use it, set an [`[up-validate]`](https://unpoly.com/api/up-validate.md) attribute on every field that should be validated with the server. In the registration form above, that is both fields:

```html
<form method="post" action="/users" up-submit>

  <fieldset>
    <label for="email">E-mail</label>
    <input type="text" id="email" name="email" up-validate>
  </fieldset>

  <fieldset>
    <label for="password">Password</label>
    <input type="password" id="password" name="password" up-validate>
  </fieldset>

  <button type="submit">Register</button>

</form>
```

When the user changes the e-mail field and moves on, Unpoly submits the form to its `[action]` path. The request carries all current field values and an additional [`X-Up-Validate`](https://unpoly.com/api/X-Up-Validate.md) header. The server is expected to validate the input without saving it, and to render the form again with any errors. Unpoly then updates only the [form group](#form-groups) around the changed field, which is the `<fieldset>` here.

To validate all fields in a form, set [`[up-validate]`](https://unpoly.com/api/up-validate.md) on the `<form>` element instead. You can also set it on any container to validate the fields within.

To submit the form whenever a field changes, like in a filter bar, see [Submitting when a field changes](https://unpoly.com/learn/submitting-forms.md#autosubmit).

Users can change fields faster than your server responds, or submit the form while a validation is still loading. Unpoly keeps the form consistent: it merges validations into a single request, queues new validations while one is in flight, and aborts pending validations when a submission replaces the form. See [preventing race conditions](https://unpoly.com/learn/reactive-server-forms.md#race-conditions) for details.

### Handling validation requests on the server {#backend}

Validation requests need a third case in the backend code that handles the form's `[action]` path. Besides saving valid data and re-rendering an invalid form, the backend must now also render a new form state from the request parameters when it sees an [`X-Up-Validate`](https://unpoly.com/api/X-Up-Validate.md) header:

```ruby
class UsersController < ApplicationController

  def create
    user_params = params.permit(:email, :password)
    @user = User.new(user_params)
    if request.headers['X-Up-Validate']
      @user.validate
      render 'form'
    elsif @user.save
      sign_in @user
    else
      render 'form', status: :unprocessable_entity
    end
  end

end
```

The status code does not matter for validation requests. Unpoly always renders the response of a validation request, even with an error code.

See [`[up-validate]`](https://unpoly.com/api/up-validate.md#backend-protocol) for the request and response in detail.

### Which fragment is updated {#form-groups}

Unpoly only re-renders the _form group_ around the validated field, not the entire form. This keeps the user's other input, focus and scroll position intact while the request is loading.

By default, a form group is a `<fieldset>`, a `<label>` or any element with an [`[up-form-group]`](https://unpoly.com/api/up-form-group.md) attribute around the field. If the field is not within a group, the entire `<form>` is updated. See [`[up-form-group]`](https://unpoly.com/api/up-form-group.md) for details and [`up.form.config.groupSelectors`](https://unpoly.com/api/up.form.config.md#config.groupSelectors) to configure what counts as a group.

Many apps wrap each label, field and error message in a group like this:

```html
<div up-form-group>
  <label for="email">E-mail</label>
  <input type="text" id="email" name="email" up-validate>
  <div class="error">E-mail has already been taken!</div>
</div>
```

To update a different fragment instead of the form group, set the [`[up-validate]`](https://unpoly.com/api/up-validate.md) attribute to a CSS selector:

```html
<input type="text" name="email" up-validate=".email-errors">
<div class="email-errors"></div>
```

Fields that update other parts of the form this way are how [Reactive server forms](https://unpoly.com/learn/reactive-server-forms.md) are built.

## Validating while typing {#validating-while-typing}

By default, [`[up-validate]`](https://unpoly.com/api/up-validate.md) reacts to the `change` event that is emitted when the user is done editing and focuses the next field.

We can validate while the user is typing by setting an `[up-watch-event="input"]` attribute:

```html
<input name="email" up-validate up-watch-event="input" up-watch-delay="100" up-keep>
```

Note that we set some additional [watch options](https://unpoly.com/learn/watch-options.md) to improve the experience:

- The [`[up-watch-delay]`](https://unpoly.com/learn/watch-options.md#debouncing) attribute delays validation until the user has stopped typing for 100 milliseconds.
- The [`[up-keep]`](https://unpoly.com/api/up-keep.md) attribute keeps additional user input while the validation request is in flight.

If you're using this pattern a lot, you may want to configure a [macro](https://unpoly.com/api/up.macro.md) for it.

## Validating from JavaScript {#script}

To validate a field or form programmatically, pass it to [`up.validate()`](https://unpoly.com/api/up.validate.md):

```js
let field = document.querySelector('input[name=email]')
up.validate(field)
```

Like [`[up-validate]`](https://unpoly.com/api/up-validate.md), this submits the form with an [`X-Up-Validate`](https://unpoly.com/api/X-Up-Validate.md) header and updates the field's form group with the response. You can also pass any other element in the form to re-render it from the current field values:

```js
up.validate('.email-errors')
```

Multiple validations within the same form are [batched](https://unpoly.com/api/up.validate.md#batching) into a single request.

<nav aria-label="Reading order">

- [Previous: Submitting forms](https://unpoly.com/learn/submitting-forms.md)
- [Next: Reactive server forms](https://unpoly.com/learn/reactive-server-forms.md)

</nav>

