Font Face
Teva Sans Latin

Drafts & Templates
PBS Templates

Primitives & components context

Primitives & components: Teva SCS

Form Validation

v0.0.0

Back to overview

For the most validations, the HTML5 Form data validations is sufficient. When more fine grained control is needed Custom validations will do the job.


HTML5 Form data validations #

Add data-validate="true" to validate forms.

By default HTML5 Form data validation is used:

  • form needs attribute novalidate
  • input can use attribute required*
  • input can use attribute minlength / minlength if textual input.
  • input can use attribute pattern if textual input.
  • textarea can use attribute required
  • textarea can use attribute minlength

Please refer to the specific input fields for all options.

* Not in combination with attribute multiple

<!-- Form  -->
<form action="#form-38087a" autocomplete="off" class="vi-form vi-typesystem" data-validate="true" novalidate>
  <div class="form-group">
    <label for="control-a5c899">Email Address
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <input aria-describedby="help-d6b764" aria-required="true" class="form-control" id="control-a5c899" name="name-10e71b" placeholder="Email Address" required type="email">
    <div id="help-d6b764" class="vi-form__feedback vi-form__feedback--hint">Basic validation (basicly only ab@bc is needed, since [type='email'], no TLD is required.)</div>
  </div>
  <div class="form-group">
    <label for="control-1506dc">Email Address
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <input aria-describedby="help-87cdc9" aria-required="true" class="form-control" id="control-1506dc" name="name-2d1288" pattern="[^@\s]+@[^@\s]+\.[^@\s]+" placeholder="Email Address" required type="email">
    <div id="help-87cdc9" class="vi-form__feedback vi-form__feedback--hint">Pattern validation using [^@\s]+@[^@\s]+\.[^@\s]+ to require a TLD (see pattern attribute)</div>
  </div>
  <div class="form-group">
    <label for="control-bd15e4">Greet me
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <input aria-describedby="help-2c98e1" aria-required="true" class="form-control" id="control-bd15e4" name="name-697a06" pattern="hello world" placeholder="Greet me" required type="text">
    <div id="help-2c98e1" class="vi-form__feedback vi-form__feedback--hint">Pattern validation only allows 'hello world' (see pattern attibute)</div>
  </div>
  <div class="form-group">
    <label for="control-8e84d6">Say something between 5 and 10 characters.
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <textarea aria-describedby="help-d6d67a" aria-required="true" class="form-control" id="control-8e84d6" maxlength="10" minlength="5" name="name-90d2bc" placeholder="Say something between 5 and 10 characters." required></textarea>
    <div id="help-d6d67a" class="vi-form__feedback vi-form__feedback--hint">Minlength and maxlength applied</div>
  </div>
  <div class="form-group">
    <div class="custom-checkbox custom-control">
      <input aria-required="true" class="custom-control-input" id="control-b68db6" name="name-cb822c" required type="checkbox">
      <label class="custom-control-label" for="control-b68db6">
        Yes! Contact me all on available channels.
      </label>
    </div>
  </div>
  <!-- Button type: solid  -->
  <button type="submit" class="vi-btn-solid vi-btn">Hit me</button>
</form>

Validation messages #

Without any further settings the browsers default messages are displayed (depending the language of the browser/OS).

To have better control over the message the folloing data attributes are supported:

  • data-msg The default validation message
  • data-msg-success Provide positive feedback on success.

More fine grained options will be added in the future.

<!-- Form  -->
<form action="#form-6a2c55" autocomplete="off" class="vi-form vi-typesystem" data-validate="true" novalidate>
  <div class="form-group">
    <label for="control-c66b73">Greet me
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <input aria-describedby="help-4c0410" aria-required="true" class="form-control" data-msg-success="And hello back to you!" data-msg="This is not a hello world" id="control-c66b73" name="name-828ee3" pattern="hello world" placeholder="Greet me" required type="text">
    <div id="help-4c0410" class="vi-form__feedback vi-form__feedback--hint">Start typing 'hello world' to see examples of custom feedback messages.</div>
  </div>
  <!-- Button type: solid  -->
  <button type="submit" class="vi-btn-solid vi-btn">Hit me</button>
</form>

The legacy method of adding messages still work. The elements are converted to data attributes by javascript.

<!-- by placing this afther form field. -->
<div class="valid-feedback">Example of positive feedback</div>
<div class="invalid-feedback">Example of negative feedback</div>

Custom validation #

Add data-validate="true" to validate forms.

For custom validation, attach an eventHandler to form or field:

<script>
  $(...).on('validate', function(e, field, valid, invalid, eventType) {
    //... your logic ...
  }
</script>

The eventHandler takes the following arguments:

  • e jQuery eventObject
  • field jQuery Object of current form field
  • isValid callback function with arguments 'state' and 'message'
  • eventType String [submit | input | blur] depending internal initiator of callback.

The logic of the eventHandler should in a call to valid() or invalid() (or none of both)

<!-- Form  -->
<form action="#to-the-server" autocomplete="off" class="vi-form vi-typesystem" data-validate="true" id="form-6ac038" novalidate>
  <div class="form-group">
    <label for="form-6ac038--field-1">Share your secret
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <input aria-describedby="help-a6d822" aria-required="true" class="form-control" id="form-6ac038--field-1" name="name-8be925" placeholder="Say !Welcome1234" required type="text">
    <div id="help-a6d822" class="vi-form__feedback vi-form__feedback--hint">Custom validation (event attached to form)</div>
  </div>
  <div class="form-group">
    <label for="form-6ac038--field-2">Rock scissors paper
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <input aria-describedby="help-41e377" aria-required="true" class="form-control" id="form-6ac038--field-2" name="name-a62b91" placeholder="rock, scissors or paper" required type="text">
    <div id="help-41e377" class="vi-form__feedback vi-form__feedback--hint">Custom validation (event attached to field)</div>
  </div>
  <div class="form-group">
    <label for="form-6ac038--field-3">Unique username
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <input aria-describedby="help-9d1aa4" aria-required="true" class="form-control" data-msg="hmmmm" id="form-6ac038--field-3" name="name-a0ad53" placeholder="Unique username (try starlord, ironman or spiderman)" required type="text">
    <div id="help-9d1aa4" class="vi-form__feedback vi-form__feedback--hint">Custom validation (delayed)</div>
  </div>
  <!-- Button type: solid  -->
  <button type="submit" class="vi-btn-solid vi-btn">Hit me</button>
</form>

The javascript used by this example.

<!-- the form script -->
<script src="/assets/javascripts/form_teva_scs.bundle-cfe189bc9cb6629a8ad3.js"></script>
<!-- your custom validation -->
<script>
  $(function() {
    // Example 1 where we attach an eventHandler to the form
    $('#form-6ac038').on('validate', function(e, field, isValid) {
      // When attached to form, we have to check what field is currently
      // being validated.
      if (field.is('#form-6ac038--field-1')) {
        // Test the field value agains some js logic
        if (/\!Welcome1234/.test(field.val())) {
          isValid(true, 'Well done! Welcome!')
        } else {
          isValid(false, 'This is not !Welcome1234')
        }
      }
    })
    // Example 2 where we attach an eventHandler to the field
    $('#form-6ac038--field-2').on('validate', function(e, field, isValid) {
      // Test the field value agains some js logic
      switch (field.val()) {
        case 'rock':
          isValid(true, 'You choose rock!')
          break;
        case 'scissors':
          isValid(true, 'You choose scissors!')
          break;
        case 'paper':
          isValid(true, 'You choose paper!')
          break;
        default:
          isValid(false, 'Must be rock, scissors or paper.')
      }
    })
    // Example 3 where we attach an eventHandler to the field
    var timeoutId;
    $('#form-6ac038--field-3').on('validate', function(e, field, isValid, eventType) {
      // Bail if ...
      if (field.val().length < 3) {
        isValid(false, 'Username too short')
        return;
      }

      // Bail if not 'input' event
      // eventType can be 'submit' | 'input' | 'blur'
      // (We only want to simulate remote call on typing)
      if (eventType != 'input') return

      // Simulated remote call here.
      // Please Note: a form can be submitted while fetching.
      // Serverside validation is always recommended
      clearTimeout(timeoutId)
      timeoutId = setTimeout(function() {
        if (/^(starlord|ironman|spiderman)$/.test(field.val())) {
          isValid(true, 'You can use this name!')
        } else {
          isValid(false, 'Already in use')
        }
      }, 1000)
    })
  })
</script>

Examples

Here follow some examples of usage for custom validation

Checkbox Group #

HTML5 does not provide an out-of-the-box way to validate a group of checkboxes.

The group is given an id, used to scope the input fields for validation.

<!-- Form  -->
<form action="#to-the-server" autocomplete="off" class="vi-form vi-typesystem" data-validate="true" id="sg-5c6990" novalidate>
  <div class="form-group" id="sg-06d582">
    <div class="custom-checkbox custom-control">
      <input class="custom-control-input" id="control-08d299" name="name-dee787" type="checkbox">
      <label class="custom-control-label" for="control-08d299">
        Leggings sriracha pabst.
      </label>
    </div>
    <div class="custom-checkbox custom-control">
      <input class="custom-control-input" id="control-7596dd" name="name-69527a" type="checkbox">
      <label class="custom-control-label" for="control-7596dd">
        8-bit thundercats franzen.
      </label>
    </div>
    <div class="custom-checkbox custom-control">
      <input class="custom-control-input" id="control-a12898" name="name-75d228" type="checkbox">
      <label class="custom-control-label" for="control-a12898">
        Paleo tumblr bespoke.
      </label>
    </div>
    <div class="custom-checkbox custom-control" id="sg-613718">
      <input class="custom-control-input" id="control-675408" name="name-dba4b3" type="checkbox">
      <label class="custom-control-label" for="control-675408">
        Flannel roof lumbersexual.
      </label>
    </div>
  </div>
  <!-- Button type: solid  -->
  <button type="submit" class="vi-btn-solid vi-btn">Thundercats meggings</button>
</form>
<script>
  $(function() {
    $('#sg-613718').on('validate', function(e, field, isValid, eventType) {

      if (eventType != 'submit') return;

      // if all checkboxed in group checked.
      if ($('#sg-06d582 :checkbox:checked').length) {
        isValid(true)
      } else {
        isValid(false, 'Please select at least one box');
      }
    });
  })
</script>

The javascript used by this example.

<script>
  $(function() {
    $('#id-of-last-checkbox').on('validate', function(e, field, isValid, eventType) {

      if (eventType != 'submit') return;

      // if all checkboxed in group checked.
      if ($('#id-of-group :checkbox:checked').length) {
        isValid(true)
      } else {
        isValid(false, 'Please select at least one box');
      }
    });
  })
</script>

Coercive Suggestions #

To only allow values from a datalist.

<!-- Form  -->
<form action="#form-c0120a" autocomplete="off" class="vi-form vi-typesystem" data-validate="true" novalidate>
  <div class="form-group">
    <label for="sg-d93619">Country
      <span aria-hidden="true" class="vi-form__required">(required)</span></label>
    <input aria-required="true" class="form-control" id="sg-d93619" list="countries" name="name-d88b28" placeholder="Country" required type="text">
    <datalist id="countries">
      <option>Russia</option>
      <option>Germany</option>
      <option>United Kingdom</option>
      <option>France</option>
      <option>Italy</option>
      <option>Spain</option>
      <option>Ukraine</option>
      <option>Poland</option>
      <option>Romania</option>
      <option>Netherlands</option>
      <option>Belgium</option>
      <option>Czech Republic</option>
      <option>Greece</option>
      <option>Portugal</option>
      <option>Sweden</option>
      <option>Hungary</option>
      <option>Belarus</option>
      <option>Austria</option>
      <option>Serbia</option>
      <option>Switzerland</option>
      <option>Bulgaria</option>
      <option>Denmark</option>
      <option>Finland</option>
      <option>Slovakia</option>
      <option>Norway</option>
      <option>Ireland</option>
      <option>Croatia</option>
      <option>Moldova</option>
      <option>Bosnia and Herzegovina</option>
      <option>Albania</option>
      <option>Lithuania</option>
      <option>North Macedonia</option>
      <option>Slovenia</option>
      <option>Latvia</option>
      <option>Estonia</option>
      <option>Montenegro</option>
      <option>Luxembourg</option>
      <option>Malta</option>
      <option>Iceland</option>
      <option>Andorra</option>
      <option>Monaco</option>
      <option>Liechtenstein</option>
      <option>San Marino</option>
      <option>Holy See</option>
    </datalist>
  </div>
  <!-- Button type: solid  -->
  <button type="submit" class="vi-btn-solid vi-btn">Twee +1</button>
</form>
<script>
  $(function() {
    $('#sg-d93619').on('validate', function(e, field, isValid, eventType) {

      // Build array of allowed values from related datalist
      var options = $('#' + field.attr('list')).find('option').map(function(index, option) {
        return $(option).text();
      }).toArray()

      // If the array includes the fields value
      if (options.includes(field.val())) {
        isValid(true)
      } else {
        isValid(false, 'Value must equal suggestion');
      }
    });
  })
</script>

The javascript used by this example.

<script>
  $(function() {
    $('#id-of-the-field-with-the-datalist').on('validate', function(e, field, isValid, eventType) {

      // Build array of allowed values from related datalist
      var options = $('#' + field.attr('list')).find('option').map(function(index, option) {
        return $(option).text();
      }).toArray()

      // If the array includes the fields value
      if (options.includes(field.val())) {
        isValid(true)
      } else {
        isValid(false, 'Value must equal suggestion');
      }
    });
  })
</script>

Stylesheets #

The following stylesheets are required to display this component.


JavaScript #

The following javascript is required to display this component.


Usage documentation #

Usage documentation can be found here.


Changelog #

Changelog

Fix

  • 19 Feb 2025 - fix(form): resolve mutation observer recursion issue in Safari
  • 13 Feb 2025 - feat(form): fix form reset validation messages
  • 11 Sep 2024 - Fix broken label issue (webkit only!)
  • 01 Jun 2023 - Fix read-only styling to input, select and textarea. Fixes placeholder combobox.
  • 23 May 2023 - Removed a11y default success message; Business requirement.
  • 28 Apr 2023 - Fix safaris hard disable color override.
  • 15 Feb 2023 - Combobox validation
  • 18 Jan 2023 - Fix background-clip setting.
  • 28 June 2022 - Fix scrollparent detector
  • Make Safari (the new IE6) understand how parenthesis work when direction is rtl.
  • Remove previous work around.
  • Try to work around chrome mobile bug (1197882) where long datalists overlap keyboard.
  • Fix chrome bug (674447, 849616) where datalist gets detached by scrolling.
  • Wrapping of lang filenames in file field.
  • PBS support: fix box-shadow on custom-control
  • Regression: accent colors not picked up by sass.
  • Allow re-enabled fields to validate.
  • Remove file field blur validation.
  • A11y: …show hidden span tag with this value”name of the file + “selected” add role=”alert”
  • A11y: Redundantly add attribute aria-required=true to elements with attribute required
  • A11y: …add a hidden span tag with this value”name of the file + “uploaded” add role=”alert”
  • Form Validation message not showing, when element hidden.
  • IE11 premature error throwing.
  • Better align custom indicators regardless font / line-height used.
  • Remove background color IE11 from focused select.

Changed

  • 09 Jan 2025 - feat(multiple-select): Show options with empty value.
  • 30 Jun 2023 - Disabled state of checkbox/radio and switch.

Added

  • 28 Aug 2024 - Add dynamic form fields example + added $.fn.viFormDynamicFieldsContainer() to assign to a dynamic fields container and keep validation in sync.
  • 17 May 2023 - Form reset removes validation messages.
  • 15 Mar 2023 - Allow browse button text to be changed.
  • 15 Feb 2023 - Combobox reset
  • 2 Dec 2022 - Add size sm size to text-fields.
  • 14 Nov 2022 - Add data attribute data-keyup to allow for keyup validation on a field. (required by wizard#contextual-fields)
  • Add an example of a paragraph
  • An example of labels on select fields.
  • PBS support
  • Examples of contextual form fields.
  • Add --top modifier to .custom-control for long labels.
  • A11y: File field focus outline same as links.
  • On submit focus on first invalid element
  • Missing File Browser field
  • RTL support
  • Documentation
  • Changed checkbox outline according new design input.
  • Initial draft