<validated-form>

A Web Component that brings automatic browser form validation to your markup, displaying accessible error messages with zero configuration or frameworks.

Source code & documentation

Demo

This demo showcases the capabilities of the <validated-form> component, demonstrating various form fields and validation scenarios. Try submitting the form with different inputs to see how it handles validation and error messages.

Requires a valid email format.

Minimum 8 characters including letter, number, and symbol.

Enter an even number between 2 and 10.

Choose one value.

Select at least one value.

Single choice group (radio buttons)

Exactly one option must be selected.

Checkbox group

Option A is required to proceed

Accepted formats: PDF, JPG, PNG.

Not required, but must be between 20 and 300 characters if filled.

Password field with custom validation messages for required and too-short errors.

This field is disabled. Although it is required, it will not trigger validation errors since it's not interactive.

Options

Use these controls to change how the <validated-form> demo behaves.

Focus behavior
Error reporting
Code sample

HTML

The HTML markup for the demo form includes a variety of form controls, each with different validation requirements. The form is wrapped in the <validated-form> component, which handles the validation logic and error message display. Each form control uses aria-errormessage to reference an error element by its id, where the validation message is displayed when the control is invalid. Descriptive text is associated separately using aria-describedby to guide users on the expected input and validation rules.

<validated-form>
  <form id="demo-form" autocomplete="off">
    <p>Fields marked with * are required.</p>

    <!-- Text / Email -->
    <div>
      <label for="text-input">Text input (email type)</label>
      <p id="text-input-hint">Requires a valid email format.</p>
      <input
        type="email"
        id="text-input"
        name="text-input"
        required
        aria-describedby="text-input-hint"
        aria-errormessage="text-input-error"
      >
      <div id="text-input-error" class="error" hidden></div>
    </div>

    <!-- Pattern -->
    <div>
      <label for="pattern-input">Pattern-validated input</label>
      <p id="pattern-input-hint">Minimum 8 characters including letter, number, and symbol.</p>
      <input
        type="text"
        id="pattern-input"
        name="pattern-input"
        pattern="^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z\d]).{8,}$"
        required
        aria-describedby="pattern-input-hint"
        aria-errormessage="pattern-input-error"
      >
      <div id="pattern-input-error" class="error" hidden></div>
    </div>

    <!-- Number -->
    <div>
      <label for="number-input">Number input</label>
      <p id="number-input-hint">Enter an even number between 2 and 10.</p>
      <input
        type="number"
        id="number-input"
        name="number-input"
        required
        min="2"
        max="10"
        step="2"
        aria-describedby="number-input-hint"
        aria-errormessage="number-input-error"
      >
      <div id="number-input-error" class="error" hidden></div>
    </div>

    <!-- Select -->
    <div>
      <label for="select-single">Single select</label>
      <p id="select-single-hint">Choose one value.</p>
      <select
        id="select-single"
        name="select-single"
        required
        aria-describedby="select-single-hint"
        aria-errormessage="select-single-error"
      >
        <option value="">Select an option</option>
        <option value="option-a">Option A</option>
        <option value="option-b">Option B</option>
        <option value="option-c">Option C</option>
      </select>
      <div id="select-single-error" class="error" hidden></div>
    </div>

    <!-- Multi Select -->
    <div>
      <label for="select-multiple">Multiple select</label>
      <p id="select-multiple-hint">Select at least one value.</p>
      <select
        id="select-multiple"
        name="select-multiple"
        multiple
        required
        aria-describedby="select-multiple-hint"
        aria-errormessage="select-multiple-error"
      >
        <option value="option-a">Option A</option>
        <option value="option-b">Option B</option>
        <option value="option-c">Option C</option>
        <option value="option-d">Option D</option>
      </select>
      <div id="select-multiple-error" class="error" hidden></div>
    </div>

    <!-- Radio -->
    <fieldset aria-describedby="radio-hint">
      <legend>Single choice group (radio buttons)</legend>
      <p id="radio-hint">Exactly one option must be selected.</p>
      <label class="inline-option">
        <input
          type="radio"
          name="radio-group"
          value="option-a"
          required
          aria-errormessage="radio-group-error"
        >
        Option A
      </label>
      <label class="inline-option">
        <input
          type="radio"
          name="radio-group"
          value="option-b"
          aria-errormessage="radio-group-error"
        >
        Option B
      </label>
      <label class="inline-option">
        <input
          type="radio"
          name="radio-group"
          value="option-c"
          aria-errormessage="radio-group-error"
        >
        Option C
      </label>
      <div id="radio-group-error" class="error" hidden></div>
    </fieldset>

    <!-- Checkbox -->
    <fieldset>
      <legend>Checkbox group</legend>
      <p id="checkbox-hint">Option A is required to proceed</p>
      <label>
        <input
          type="checkbox"
          name="checkbox-input-required"
          value="option-a"
          required
          aria-describedby="checkbox-hint"
          aria-errormessage="checkbox-input-required-error"
        >
        Option A
      </label>
      <div id="checkbox-input-required-error" class="error" hidden></div>
      <label>
        <input
          type="checkbox"
          name="checkbox-input-optional"
          value="option-b"
        >
        Option B (Optional)
      </label>
    </fieldset>

    <!-- File -->
    <div>
      <label for="file-input">File upload</label>
      <p id="file-input-hint">Accepted formats: PDF, JPG, PNG.</p>
      <input
        type="file"
        id="file-input"
        name="file-input"
        required
        accept=".pdf,.jpg,.jpeg,.png"
        aria-describedby="file-input-hint"
        aria-errormessage="file-input-error"
      >
      <div id="file-input-error" class="error" hidden></div>
    </div>

    <!-- Textarea -->
    <div>
      <label for="textarea-input">Textarea</label>
      <p id="textarea-hint">Not required, but must be between 20 and 300 characters if filled.</p>
      <textarea
        id="textarea-input"
        name="textarea-input"
        rows="4"
        minlength="20"
        maxlength="300"
        aria-describedby="textarea-hint"
        aria-errormessage="textarea-input-error"
      ></textarea>
      <div id="textarea-input-error" class="error" hidden></div>
    </div>

    <!-- Custom validation errors -->
    <div>
      <label for="custom-errors-input">Input with custom error messages</label>
      <p id="custom-errors-input-hint">Password field with custom validation messages for required and too-short errors.</p>
      <input
        type="password"
        id="custom-errors-input"
        name="custom-errors-input"
        required
        minlength="8"
        autocomplete="off"
        aria-describedby="custom-errors-input-hint"
        aria-errormessage="custom-errors-input-error"
        data-msg-required="Password is required."
        data-msg-too-short="Password must be at least 8 characters."
      >
      <div id="custom-errors-input-error" class="error" hidden></div>
    </div>

    <!-- Disabled -->
    <div>
      <label for="disabled-input">Disabled control</label>
      <p id="disabled-input-hint">This field is disabled. Although it is required, it will not trigger validation errors since it's not interactive.</p>
      <input
        type="text"
        id="disabled-input"
        name="disabled-input"
        disabled
        required
        aria-describedby="disabled-input-hint"
        aria-errormessage="disabled-input-error"
      >
      <div id="disabled-input-error" class="error" hidden></div>
    </div>

    <button type="submit">Submit</button>
    <button type="reset">Reset</button>
  </form>
</validated-form>

CSS

The component does not include any default styles for error messages, allowing you to style them as needed. The following CSS snippet provides a basic example using the error class applied to the error elements. The styling of the form is omitted for brevity, but you can apply your own styles to the form controls and layout as desired.

.error {
  color: light-dark(#ae1a28, #ff7d89);
  font-size: 0.875rem;
  word-wrap: break-word;
}

JavaScript

The JavaScript code listens for the form's submit event, prevents the default submission behavior, and checks if the form is valid using the isValid() method provided by the <validated-form> component. If the form is valid, it collects the form data into a FormData object and logs it to the console. You can replace the console log with any custom submission logic, such as sending the data to a server or displaying a success message.

const validatedForm = document.querySelector('validated-form');
const form = document.querySelector('form');

form.addEventListener('submit', evt => {
  evt.preventDefault();

  if (!validatedForm.isValid?.()) {
    return;
  }

  const formData = new FormData(form);
  const data = Object.fromEntries(formData.entries());
  console.log('Form data:', data);
});

form.addEventListener('reset', evt => {
  validatedForm.resetValidation?.();
});

Form submitted successfully!

The form has passed validation and was submitted without errors.

License

Licensed under the The MIT License (MIT)