Edit this page

up.form [up-validate]
HTML selector

Renders a new form state when a field changes, to show validation errors or update dependent elements.

When a form field with an [up-validate] attribute is changed, the form is submitted to the server which is expected to render a new form state from its current field values. The form group around the changed field is updated with the server response. This quickly signals whether a change is valid, without the need to scroll for error messages or to backtrack to fields completed earlier.

Note

[up-validate] is a tool to implement highly dynamic forms that must update as the user is completing fields.
If you only need to validate forms after submission, you don't need [up-validate].

Marking fields for validation

Let's look at a standard registration form that asks for an e-mail and password. The form is organized into form groups of labels, inputs and an optional error message:

<form action="/users">

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

  <fieldset> <!-- mark: fieldset -->
    <label for="password">Password</label>
    <input type="password" id="password" name="password">
  </fieldset> <!-- mark: fieldset -->

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

</form>

We have some data constraints that we want to validate as the user is filling in fields:

  • When the user changes the email field, we want to validate that the e-mail address is formatted correctly and is still available.
  • When the user changes the password field, we want to validate the minimum password length.

If validation fails we want to show validation errors as soon as the user blurs the field.

We're going to render validation errors using the following HTML:

<form action="/users">

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

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

</form>

We can implement this by giving both fields an [up-validate] attribute:

<form action="/users">

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

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

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

</form>

Whenever a field with [up-validate] changes, the form is submitted to its [action] path with an additional X-Up-Validate HTTP header.

Read on to learn how a validation request is sent to the server, and how the server response is displayed.

Backend protocol

When the user changes the email field in the registration form above, the following request will be sent:

POST /users HTTP/1.1
X-Up-Validate: email
X-Up-Target: fieldset:has(#email)
Content-Type: application/x-www-form-urlencoded

email=foo%40bar.com&password=secret

Upon seeing an X-Up-Validate header, the server is expected to validate (but not commit) the form submission and render a new form state from the request parameters.

This requires a change in the backend code that handles the form's [action] path. Until now the backend only had 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 email or password. We re-render the form with error messages.

A Ruby on Rails implementation would look like this:

class UsersController < ApplicationController

  def create
    # Instantiate model from request parameters
    user_params = params.require(:user).permit(:email, :password)
    @user = User.new(user_params)

    if @user.save
      # Form is submitted successfully
      sign_in @user
    else
      # Submission failed
      render 'form', status: :unprocessable_entity
    end

  end

end

To honor the validation protocol, our backend needs to handle a third case:

  1. When seeing an X-Up-Validate header, render a new form state from request parameters

In our example backend above, this change could look like this:

class UsersController < ApplicationController

  def create
    # Instantiate model from request parameters
    user_params = params.require(:user).permit(:email, :password)
    @user = User.new(user_params)

    if request.headers['X-Up-Validate'] # mark-line
      @user.validate # mark-line
      render 'form' # mark-line
    elsif @user.save
      # Form is submitted successfully
      sign_in @user
    else
      # Submission failed
      render 'form', status: :unprocessable_entity
    end

  end

end

Tip

If you're using Python with Django, you may find the django-forms-dynamic package useful to implement this pattern.

The server is free to respond with any HTTP status code, regardless of the validation result. Unpoly will always consider a validation request to be successful, even if the server responds with a non-200 status code.

Upon seeing an X-Up-Validate header, the server now renders a new state form from request parameters, showing eventual validation errors and updating dependent elements:

<form action="/users">

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

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

</form>

How validation results are displayed

[up-validate] always submits the entire form with its current field values to the form's [action] path. Typically only a fragment of the form is updated with the response. This minimizes the chance for loss of transient state like scroll positions, cursor selection or user input while the request is in flight.

By default Unpoly will only update the closest form group around the validating field. The example above, after changing the email field, only the <fieldset> around the field will be updated.

If the form is not structured into groups, the entire form will be updated.

Updating a different fragment

If you don't want to update the field's form group, you can set the [up-validate] attribute to any target selector:

<input name="email" up-validate=".email-errors"> <!-- mark: .email-errors -->
<div class="email-errors"></div>

You may update multiple fragments by separating their target selectors with a comma:

<input name="email" up-validate=".email-errors, .base-errors"> <!-- mark: .email-errors, .base-errors -->

To update another fragment in addition to the field's form group, include the group in the target list.
You can refer to the changed field as :origin:

<fieldset>
  <input name="email" up-validate="fieldset:has(:origin), .base-errors"> <!-- mark: fieldset:has(:origin), .base-errors -->
</fieldset>

Updating dependent elements

The [up-validate] attribute is a useful tool to partially update a form when an element depends on the value of another field.

See Reactive server forms for more details and examples.

Validating while typing

By default [up-validate] 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:

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

Note that we set some additional watch options to improve the experience:

  • The [up-watch-delay] attribute to delay validation until the user has stopped typing for 100 milliseconds.
  • The [up-keep] attribute to preserve 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 for it.

Preventing race conditions

Custom dynamic form implementations will often exhibit race conditions, e.g. when the user is quickly changing fields while requests are still in flight. Such issues are solved with [up-validate]. The form will eventually show a consistent state, regardless of how fast the user clicks or how slow the network is.

See preventing race conditions for more details.

Validating multiple fields

You can set [up-validate] on any element to validate all contained fields on change.

In the example above, instead of setting [up-validate] on each individual <input>, we can also set it on the <form>:

<form action="/users" up-validate> <!-- mark: up-validate -->

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

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

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

</form>

You can also set [up-validate] on an intermediate container to only validate its children:

<form action="/users">

  <div up-validate> <!-- mark: up-validate -->
    <fieldset>
      <label for="email">E-mail</label>
      <input type="text" id="email" name="email"> <!-- chip "will validate" -->
    </fieldset>

    <fieldset>
      <label for="password">Password</label>
      <input type="password" id="password" name="password"> <!-- chip "will validate" -->
    </fieldset>
  </div>

  <fieldset>
    <label for="name">Name</label>
    <input type="name" id="name" name="name"> <!-- chip "will NOT validate" -->
  </fieldset>

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

</form>

Validating radio buttons

Multiple radio buttons with the same [name] produce a single value for the form.

To watch radio buttons group, use the [up-validate] attribute on an element that contains all radio button elements with a given name:

<fieldset up-validate>
  <input type="radio" name="format" value="html"> HTML format
  <input type="radio" name="format" value="pdf"> PDF format
  <input type="radio" name="format" value="txt"> Text format
</fieldset>

Validating against other URLs

By default, validation requests will use [method] and [action] attributes from the form element.

You can validate against another server endpoint by setting [up-validate-url] and [up-validate-method] attributes:

<form method="post" action="/order" up-validate-url="/validate-order"> <!-- mark: up-validate-url -->
  ...
</form>

To have individual fields validate against different URLs, you can also set [up-validate-url] on a field:

<form method="post" action="/register">
  <input name="email" up-validate-url="/validate-email"> <!-- mark: /validate-email -->
  <input name="password" up-validate-url="/validate-password"> <!-- mark: /validate-password -->
</form>

Multiple validations to the same URL will be batched together.

Programmatic validation

To update form fragments from your JavaScript, use the up.validate() function. You may combine [up-validate] and up.validate() within the same form. Their updates will be batched together in order to prevent race conditions.


Targeting

[up-validate]
optional

The target selector to update with the server response.

Defaults the closest form group around the validating field.

You can set another selector to update a different fragment. To refer to the changed field, use the :origin pseudo-selector.

Observed events

[up-watch-event='change']
optional

The type of event to watch.

See which events to watch.

[up-watch-delay=0]
optional

The number of milliseconds to wait after a change.

This can be used to batch multiple events within a short time span. See debouncing callbacks.

Request

[up-validate-url]
optional

The URL to which to submit the validation request.

By default Unpoly will use the form's [action] attribute

See Validating against other URLs.

[up-validate-method]
optional

The method to use for submitting the validation request.

By default Unpoly will use the form's [method] attribute

See Validating against other URLs.

[up-validate-params]
optional

Additional Form parameters that should be sent as the request's query string or payload.

The given value will be added to params parsed from the form's input fields. If a param with the same name already existed in the form, it will be deleted and overridden with the given value.

[up-validate-headers]
optional

A relaxed JSON object with additional request headers.

By default Unpoly will send an X-Up-Validate header so the server can distinguish the validation request from a regular form submission.

[up-validate-batch='true']
optional

Loading state

[up-watch-disable]
optional

Whether to disable fields while a request is loading.

See disabling fields while working.

[up-watch-placeholder]
optional

A placeholder to show within the targeted fragment while a request is loading.

See showing loading state while working.

[up-watch-preview]
optional

One or more previews that temporarily change the page while a request is loading.

See showing loading state while working.

[up-watch-feedback='true']
optional

Whether to set feedback classes while a request is loading.