Instant Form Validation

We check on the fly whether the user has filled out the form fields correctly.

Time to read: over 15 min

Task

Who among us is not familiar with that unpleasant situation when you carefully fill out a form, enter data, then, hoping to click the "Submit" button, you find out that something went wrong and all your efforts were in vain? There's a solution for this - instant validation with JavaScript!

An excellent user experience is the key to success. Form validation with HTML cannot provide the level of UX (user experience) required by web development standards. JavaScript validation comes to the rescue. It provides instant feedback while filling out the form and neatly hints at what needs to be corrected before the form is submitted.

Ready Solution

Example of standard HTML markup for a form:

        
          
          <form  class="form"  name="form"  method="POST"  novalidate>  <div class="form__field-container">    <label class="form__field">      <span class="form__label">Name:</span>      <input        type="text"        id="input__name"        class="form__type-input"        placeholder="Ivan"        pattern="^[a-zA-Zа-яА-ЯЁё \-]+$"        data-error-message="Allowed symbols are Latin,        Cyrillic, hyphens, and spaces."        aria-describedby="name-error"        required      >    </label>    <span      class="form__error      input__name-error"      id="name-error"      aria-live="polite"    >    </span>  </div>  <div class="form__field-container">    <label class="form__field">      <span class="form__label">Surname:</span>      <input        type="text"        id="input__surname"        class="form__type-input"        placeholder="Vasilievich"        pattern="^[a-zA-Zа-яА-ЯЁё\s\-]+$"        data-error-message="Allowed symbols are Latin,        Cyrillic, hyphens, and spaces."        aria-describedby="surname-error"        required      >    </label>    <span      class="form__error      input__surname-error"      id="surname-error"      aria-live="polite"    >    </span>  </div>  <div class="form__field-container">    <label class="form__field">      <span class="form__label">Email:</span>      <input        type="email"        id="input__e-mail"        class="form__type-input"        placeholder="menyaet.professiyu@ivan.com"        aria-describedby="email-error"        required      >    </label>    <span      class="form__error input__e-mail-error"      id="email-error"      aria-live="polite"    >    </span>  </div>  <div class="form__field-container">    <label class="form__field">      <span class="form__label">Age:</span>      <input        type="number"        id="input__age"        class="form__type-input"        placeholder="40"        min="18"        max="100"        aria-describedby="age-error"        required      >    </label>    <span      class="form__error input__age-error"      id="age-error"      aria-live="polite"    >    </span>  </div>  <div class="form__field-container">    <label class="form__checkbox-label">      <input        type="checkbox"        id="input__checkbox"        class="form__type-checkbox"        checked        aria-describedby="checkbox-error"        required      />      <span class="form__type-checkbox-title">        I agree to be a king      </span>    </label>    <span class="form__error input__checkbox-error" id="checkbox-error" aria-live="polite"></span>  </div>  <button    type="submit"    class="button"    aria-describedby="empty-error"  >    Submit  </button>  <span    class="form__empty-error"    id="empty-error"    aria-live="assertive"  >  </span></form>
          <form
  class="form"
  name="form"
  method="POST"
  novalidate
>
  <div class="form__field-container">
    <label class="form__field">
      <span class="form__label">Name:</span>
      <input
        type="text"
        id="input__name"
        class="form__type-input"
        placeholder="Ivan"
        pattern="^[a-zA-Zа-яА-ЯЁё \-]+$"
        data-error-message="Allowed symbols are Latin,
        Cyrillic, hyphens, and spaces."
        aria-describedby="name-error"
        required
      >
    </label>
    <span
      class="form__error
      input__name-error"
      id="name-error"
      aria-live="polite"
    >
    </span>
  </div>
  <div class="form__field-container">
    <label class="form__field">
      <span class="form__label">Surname:</span>
      <input
        type="text"
        id="input__surname"
        class="form__type-input"
        placeholder="Vasilievich"
        pattern="^[a-zA-Zа-яА-ЯЁё\s\-]+$"
        data-error-message="Allowed symbols are Latin,
        Cyrillic, hyphens, and spaces."
        aria-describedby="surname-error"
        required
      >
    </label>
    <span
      class="form__error
      input__surname-error"
      id="surname-error"
      aria-live="polite"
    >
    </span>
  </div>
  <div class="form__field-container">
    <label class="form__field">
      <span class="form__label">Email:</span>
      <input
        type="email"
        id="input__e-mail"
        class="form__type-input"
        placeholder="menyaet.professiyu@ivan.com"
        aria-describedby="email-error"
        required
      >
    </label>
    <span
      class="form__error input__e-mail-error"
      id="email-error"
      aria-live="polite"
    >
    </span>
  </div>
  <div class="form__field-container">
    <label class="form__field">
      <span class="form__label">Age:</span>
      <input
        type="number"
        id="input__age"
        class="form__type-input"
        placeholder="40"
        min="18"
        max="100"
        aria-describedby="age-error"
        required
      >
    </label>
    <span
      class="form__error input__age-error"
      id="age-error"
      aria-live="polite"
    >
    </span>
  </div>
  <div class="form__field-container">
    <label class="form__checkbox-label">
      <input
        type="checkbox"
        id="input__checkbox"
        class="form__type-checkbox"
        checked
        aria-describedby="checkbox-error"
        required
      />
      <span class="form__type-checkbox-title">
        I agree to be a king
      </span>
    </label>
    <span class="form__error input__checkbox-error" id="checkbox-error" aria-live="polite"></span>
  </div>
  <button
    type="submit"
    class="button"
    aria-describedby="empty-error"
  >
    Submit
  </button>
  <span
    class="form__empty-error"
    id="empty-error"
    aria-live="assertive"
  >
  </span>
</form>

        
        
          
        
      

JavaScript code for validating all fields of the form:

        
          
          const form = document.querySelector('.form')const inputList = Array.from(form.querySelectorAll('.form__type-input'))const checkboxElement = form.querySelector('.form__type-checkbox')const buttonElement = form.querySelector('.button')const formErrorElement = form.querySelector('.form__empty-error')startValidation()function startValidation() {  toggleButton()  form.addEventListener('submit', (event) => {    event.preventDefault()    if (hasInvalidInput()) {      formError()      inputList.forEach((inputElement) => {        checkInputValidity(inputElement)        toggleInputError(inputElement)      })      toggleInputError(checkboxElement)    }  })  inputList.forEach((inputElement) => {    inputElement.addEventListener('input', () => {      checkInputValidity(inputElement)      toggleButton()    })    inputElement.addEventListener('blur', () => {      toggleInputError(inputElement)    })    inputElement.addEventListener('focus', () => {      toggleErrorSpan(inputElement)    })    checkboxElement.addEventListener('change', () => {      toggleInputError(checkboxElement)      toggleButton()    })  })}function checkInputValidity(inputElement) {  if (inputElement.validity.patternMismatch) {    inputElement.setCustomValidity(inputElement.dataset.errorMessage)  } else {    inputElement.setCustomValidity(checkLengthMismatch(inputElement))  }}function checkLengthMismatch(inputElement) {  if (inputElement.type !== 'text') {    return ''  }  const valueLength = inputElement.value.trim().length  if (valueLength < inputElement.minLength) {    return `Minimum number of characters: ${inputElement.minLength}`  }  return ''}function hasInvalidInput() {  return (    inputList.some(inputElement => !inputElement.validity.valid) || !checkboxElement.validity.valid  )}function toggleErrorSpan(inputElement, errorMessage){  const errorElement = document.querySelector(`.${inputElement.id}-error`)  if (errorMessage) {    inputElement.classList.add('form__type-input-error')    errorElement.textContent = errorMessage    errorElement.classList.add('form__error-active')  } else {    inputElement.classList.remove('form__type-input-error')    errorElement.textContent = ''    errorElement.classList.remove('form__error-active')  }}function toggleButton() {  if (hasInvalidInput()) {    buttonElement.classList.add('button-inactive')    buttonElement.setAttribute('aria-disabled', 'true')  } else {    buttonElement.classList.remove('button-inactive')    buttonElement.setAttribute('aria-disabled', 'false')    formErrorElement.textContent = ''  }}function formError() {  const errorMessage = 'Please fill in all fields to submit the form.'  formErrorElement.textContent = errorMessage}
          const form = document.querySelector('.form')
const inputList = Array.from(form.querySelectorAll('.form__type-input'))
const checkboxElement = form.querySelector('.form__type-checkbox')
const buttonElement = form.querySelector('.button')
const formErrorElement = form.querySelector('.form__empty-error')

startValidation()

function startValidation() {
  toggleButton()
  form.addEventListener('submit', (event) => {
    event.preventDefault()
    if (hasInvalidInput()) {
      formError()
      inputList.forEach((inputElement) => {
        checkInputValidity(inputElement)
        toggleInputError(inputElement)
      })
      toggleInputError(checkboxElement)
    }
  })
  inputList.forEach((inputElement) => {
    inputElement.addEventListener('input', () => {
      checkInputValidity(inputElement)
      toggleButton()
    })
    inputElement.addEventListener('blur', () => {
      toggleInputError(inputElement)
    })
    inputElement.addEventListener('focus', () => {
      toggleErrorSpan(inputElement)
    })
    checkboxElement.addEventListener('change', () => {
      toggleInputError(checkboxElement)
      toggleButton()
    })
  })
}

function checkInputValidity(inputElement) {
  if (inputElement.validity.patternMismatch) {
    inputElement.setCustomValidity(inputElement.dataset.errorMessage)
  } else {
    inputElement.setCustomValidity(checkLengthMismatch(inputElement))
  }
}

function checkLengthMismatch(inputElement) {
  if (inputElement.type !== 'text') {
    return ''
  }
  const valueLength = inputElement.value.trim().length
  if (valueLength < inputElement.minLength) {
    return `Minimum number of characters: ${inputElement.minLength}`
  }
  return ''
}

function hasInvalidInput() {
  return (
    inputList.some(inputElement => !inputElement.validity.valid) || !checkboxElement.validity.valid
  )
}

function toggleErrorSpan(inputElement, errorMessage){
  const errorElement = document.querySelector(`.${inputElement.id}-error`)
  if (errorMessage) {
    inputElement.classList.add('form__type-input-error')
    errorElement.textContent = errorMessage
    errorElement.classList.add('form__error-active')
  } else {
    inputElement.classList.remove('form__type-input-error')
    errorElement.textContent = ''
    errorElement.classList.remove('form__error-active')
  }
}

function toggleButton() {
  if (hasInvalidInput()) {
    buttonElement.classList.add('button-inactive')
    buttonElement.setAttribute('aria-disabled', 'true')
  } else {
    buttonElement.classList.remove('button-inactive')
    buttonElement.setAttribute('aria-disabled', 'false')
    formErrorElement.textContent = ''
  }
}

function formError() {
  const errorMessage = 'Please fill in all fields to submit the form.'
  formErrorElement.textContent = errorMessage
}

        
        
          
        
      

CSS styles to be used during validation:

        
          
          /* To change the border color of the form element during validation */.form__type-input-error {  border: 1px solid #FF8630;  background-color: rgb(255 134 48 / 0.1);}/* To display the span element with the error */.form__error-active {  display: block;}/* To disable the submit button */.button-inactive {  cursor: default;  background-color: rgb(211 211 211 / 0.6);}
          /* To change the border color of the form element during validation */
.form__type-input-error {
  border: 1px solid #FF8630;
  background-color: rgb(255 134 48 / 0.1);
}

/* To display the span element with the error */
.form__error-active {
  display: block;
}

/* To disable the submit button */
.button-inactive {
  cursor: default;
  background-color: rgb(211 211 211 / 0.6);
}

        
        
          
        
      
Open demo in the new window

Analysis of the Solution

First, we inform the browser that it should not validate the form in the standard way by adding the novalidate attribute to the <form> tag.

        
          
          <form class="form__field" novalidate>  <!-- Form content --></form>
          <form class="form__field" novalidate>
  <!-- Form content -->
</form>

        
        
          
        
      

Markup

Look at the example of the markup of the form element to better understand how validation works.

We add attributes:

  1. type - defines the expected type of data in the field.
  2. placeholder - provides a hint to the user about what data to enter.
  3. required - indicates the necessity of filling out the field.

We link the input field and the <span> with an error using identifiers and CSS classes. We assign an identifier to <input> and give the same class for <span>, adding '-error' at the end. This will allow finding the <span> in the DOM by such a scheme: document.querySelector(${input.id}-error). To make this connection between the field and its error clear for users of assistive technologies, we associate them through the aria-describedby attribute in the field and the button along with the id of the same value in <span>. To ensure assistive technologies automatically announce them, we additionally add another ARIA attribute aria-live.

We configure the validation parameters. Standard attributes like maxlength/minlength can be used here, as well as non-standard attributes such as pattern with regular expressions. The latter allows configuring more precise and specific rules for input fields.

More about pattern: Although in most cases, standard validation messages are sufficient, there are times when more specific requirements for input fields are necessary. For instance, the case where only letters of the Latin and Cyrillic alphabet, hyphens, and spaces are allowed. This set of symbols is not provided by standard validation, making custom validation necessary. We use a regular expression and store the custom error message in a specially created data attribute - 'data-error-message'. More about data attributes can be read in the documentation for data-* attributes.

        
          
          <label class="form__field">  <span class="form__label">Name:</span>  <input    type="text"    id="input__name"    class="form__type-input"    placeholder="Ivan"    pattern="^[a-zA-Zа-яА-ЯЁё \-]+$"    data-error-message="Allowed symbols are Latin,    Cyrillic, hyphens, and spaces."    aria-describedby="name-error"    required  ></label><span  class="form__error  input__name-error"  id="name-error"  aria-live="polite"></span>
          <label class="form__field">
  <span class="form__label">Name:</span>
  <input
    type="text"
    id="input__name"
    class="form__type-input"
    placeholder="Ivan"
    pattern="^[a-zA-Zа-яА-ЯЁё \-]+$"
    data-error-message="Allowed symbols are Latin,
    Cyrillic, hyphens, and spaces."
    aria-describedby="name-error"
    required
  >
</label>
<span
  class="form__error
  input__name-error"
  id="name-error"
  aria-live="polite"
>
</span>

        
        
          
        
      

JavaScript

First, we gather all the necessary DOM elements for validation:

        
          
          const form = document.querySelector('.form')const inputList = Array.from(form.querySelectorAll('.form__type-input'))const checkboxElement = form.querySelector('.form__type-checkbox')const buttonElement = form.querySelector('.button')const formErrorElement = form.querySelector('.form__empty-error')
          const form = document.querySelector('.form')
const inputList = Array.from(form.querySelectorAll('.form__type-input'))
const checkboxElement = form.querySelector('.form__type-checkbox')
const buttonElement = form.querySelector('.button')
const formErrorElement = form.querySelector('.form__empty-error')

        
        
          
        
      

The startValidation() function initiates the validation process. It adds an event listener for the entire form on the submit event, where event.preventDefault() is used to prevent the form's standard behavior upon submission. For more information, read "Working with Forms" (/js/deal-with-forms/).

Event handlers input, blur, and focus are assigned to each input field. Any changes in the input fields activate the functions checkInputValidity() and toggleButton(). When the focus is lost, the toggleInputError() function is activated, and when focus is gained, the error message is reset using toggleErrorSpan().
A dedicated handler change is added for the checkbox, which will trigger the functions toggleInputError() and toggleButton() when such an event occurs. All of these functions will be written later.

        
          
          function startValidation() {  form.addEventListener('submit', (event) => {    event.preventDefault()    if (hasInvalidInput()) {      formError()      inputList.forEach((inputElement) => {        checkInputValidity(inputElement)        toggleInputError(inputElement)      })      toggleInputError(checkboxElement)    }  })  inputList.forEach((inputElement) => {    inputElement.addEventListener('input', () => {      checkInputValidity(inputElement)      toggleButton()    })    inputElement.addEventListener('blur', () => {      toggleInputError(inputElement)    })    inputElement.addEventListener('focus', () => {      toggleErrorSpan(inputElement)    })  })  checkboxElement.addEventListener('change', () => {    toggleInputError(checkboxElement)    toggleButton()  })}
          function startValidation() {
  form.addEventListener('submit', (event) => {
    event.preventDefault()
    if (hasInvalidInput()) {
      formError()
      inputList.forEach((inputElement) => {
        checkInputValidity(inputElement)
        toggleInputError(inputElement)
      })
      toggleInputError(checkboxElement)
    }
  })

  inputList.forEach((inputElement) => {
    inputElement.addEventListener('input', () => {
      checkInputValidity(inputElement)
      toggleButton()
    })
    inputElement.addEventListener('blur', () => {
      toggleInputError(inputElement)
    })
    inputElement.addEventListener('focus', () => {
      toggleErrorSpan(inputElement)
    })
  })

  checkboxElement.addEventListener('change', () => {
    toggleInputError(checkboxElement)
    toggleButton()
  })
}

        
        
          
        
      

The checkInputValidity() function utilizes the JavaScript ValidityState object to check each input field. If the field is not valid, an error message is displayed.

The validityState object can be seen when accessing the validity key of the input element (input.validity). It looks like this:

        
          
          {  badInput: false,  customError: false,  patternMismatch: false,  rangeOverflow: false,  rangeUnderflow: false,  stepMismatch: false,  tooLong: false,  tooShort: false,  typeMismatch: false,  valid: true,  valueMissing: false,}
          {
  badInput: false,
  customError: false,
  patternMismatch: false,
  rangeOverflow: false,
  rangeUnderflow: false,
  stepMismatch: false,
  tooLong: false,
  tooShort: false,
  typeMismatch: false,
  valid: true,
  valueMissing: false,
}

        
        
          
        
      

For an input field to be considered valid, its property input.validity.valid must equal true. This property becomes true when all other properties in the validity object are false. More about validity and the meaning of each key in this object can be found here: MDN Web Docs - ValidityState.

In the checkInputValidity() function, the validityState object and its patternMismatch key for custom errors, as well as valid for standard checks, are used.

First, we check whether a specific pattern has been set for the input field and whether a minimum length is established. If a pattern is defined and does not match the entered data, the setCustomValidity function is used to pass the custom error message stored in the data-error-message attribute. If the entered data matches the pattern, the function checkLengthMismatch() also checks the length of the entered data, stripped of spaces. If the message exceeds the established character limit and is not empty, the error message is not transmitted; otherwise, the user receives a message with the minimum required number of characters.

The toggleInputError() function implements standard validation: if input.validity.valid is false, an error message is displayed, and if true, the error is removed.

        
          
          function checkInputValidity(inputElement) {  if (inputElement.validity.patternMismatch) {    inputElement.setCustomValidity(inputElement.dataset.errorMessage)  } else {    inputElement.setCustomValidity(checkLengthMismatch(inputElement))  }}function checkLengthMismatch(inputElement) {  if (inputElement.type !== 'text') {    return ''  }  const valueLength = inputElement.value.trim().length  if (valueLength < inputElement.minLength) {    return `Minimum number of characters: ${inputElement.minLength}`  }  return ''}function toggleInputError(inputElement) {  if (!inputElement.validity.valid) {    toggleErrorSpan(inputElement, inputElement.validationMessage)  } else {    toggleErrorSpan(inputElement)  }}
          function checkInputValidity(inputElement) {
  if (inputElement.validity.patternMismatch) {
    inputElement.setCustomValidity(inputElement.dataset.errorMessage)
  } else {
    inputElement.setCustomValidity(checkLengthMismatch(inputElement))
  }
}

function checkLengthMismatch(inputElement) {
  if (inputElement.type !== 'text') {
    return ''
  }
  const valueLength = inputElement.value.trim().length
  if (valueLength < inputElement.minLength) {
    return `Minimum number of characters: ${inputElement.minLength}`
  }
  return ''
}

function toggleInputError(inputElement) {
  if (!inputElement.validity.valid) {
    toggleErrorSpan(inputElement, inputElement.validationMessage)
  } else {
    toggleErrorSpan(inputElement)
  }
}

        
        
          
        
      

The toggleButton() function is simple: it disables the button when invalid fields are found and re-enables it when all fields are correctly filled. The hasInvalidInput() function checks the input fields and checkbox for errors and returns true or false, based on whether invalid data has been detected.

Disabling the submit button is a risky technique. It is important to consider various user behaviors. To avoid situations where the user does not understand the reason for the button being disabled, we take the following measures:

  • We apply the class button-inactive, which changes the color of the button to a less bright color, signaling to the user that pressing is impossible.
  • We add cursor: not-allowed; property through this class, changing the cursor's shape to a prohibition symbol.
  • When clicking the submit button or pressing it with the keyboard, we show an error explaining the reason for the disabling. We implement this using JavaScript.
  • If invalid data is entered into one of the fields, the user receives feedback on the error made after losing focus on the field or after removing the marker from the required checkbox.
  • As soon as the user inputs valid data, the button becomes active.
  • To make the disabled button noticeable to users navigating the site using the Tab key, we add the attribute aria-disabled to the button.

We also remind that when blocking the submit button, it is essential to ensure that the filling requirements are reasonable and achievable by all users. We should avoid establishing excessively strict conditions for the data entered by the user. For instance, a user may find their name too long for a specified limit in the form of 10 characters, which would make submitting the form impossible and limit access to your product.

        
          
          function toggleButton() {  if (hasInvalidInput()) {    buttonElement.classList.add('button-inactive')    buttonElement.setAttribute('aria-disabled', 'true')  } else {    buttonElement.classList.remove('button-inactive')    buttonElement.setAttribute('aria-disabled', 'false')    formErrorElement.textContent = ''  }}function hasInvalidInput() {  return (    inputList.some(inputElement => !inputElement.validity.valid) || !checkboxElement.validity.valid  )}function formError() {  const errorMessage = 'Please fill in all fields to submit the form.'  formErrorElement.textContent = errorMessage}
          function toggleButton() {
  if (hasInvalidInput()) {
    buttonElement.classList.add('button-inactive')
    buttonElement.setAttribute('aria-disabled', 'true')
  } else {
    buttonElement.classList.remove('button-inactive')
    buttonElement.setAttribute('aria-disabled', 'false')
    formErrorElement.textContent = ''
  }
}

function hasInvalidInput() {
  return (
    inputList.some(inputElement => !inputElement.validity.valid) || !checkboxElement.validity.valid
  )
}

function formError() {
  const errorMessage = 'Please fill in all fields to submit the form.'
  formErrorElement.textContent = errorMessage
}

        
        
          
        
      
        
          
          .button-inactive {  cursor: not-allowed;  background-color: rgb(211 211 211 / 0.6);}.button-inactive:hover {  background-color: rgb(211 211 211 / 0.2);  border: 2px solid transparent;}.form__empty-error {  padding: 10px 0;  font-size: 18px;  color: #FF8630;}
          .button-inactive {
  cursor: not-allowed;
  background-color: rgb(211 211 211 / 0.6);
}

.button-inactive:hover {
  background-color: rgb(211 211 211 / 0.2);
  border: 2px solid transparent;
}

.form__empty-error {
  padding: 10px 0;
  font-size: 18px;
  color: #FF8630;
}

        
        
          
        
      

Finally, we need to activate the elements with errors. If an input field is invalid, the script shows the pre-prepared element with an error message. If the field becomes valid, the message disappears. This is where we needed the trick where we created the error class using the following template: id of input element + '-error'

        
          
          function toggleErrorSpan(inputElement, errorMessage){  const errorElement = document.querySelector(`.${inputElement.id}-error`)  if (errorMessage) {    inputElement.classList.add('form__type-input-error')    errorElement.textContent = errorMessage    errorElement.classList.add('form__error-active')  } else {    inputElement.classList.remove('form__type-input-error')    errorElement.textContent = ''    errorElement.classList.remove('form__error-active')  }}
          function toggleErrorSpan(inputElement, errorMessage){
  const errorElement = document.querySelector(`.${inputElement.id}-error`)

  if (errorMessage) {
    inputElement.classList.add('form__type-input-error')
    errorElement.textContent = errorMessage
    errorElement.classList.add('form__error-active')
  } else {
    inputElement.classList.remove('form__type-input-error')
    errorElement.textContent = ''
    errorElement.classList.remove('form__error-active')
  }
}

        
        
          
        
      

Additionally, we take care of the empty form error when clicking or pressing the keyboard on the button.

        
          
          const formErrorElement = form.querySelector('.form__empty-error')function startValidation() {  form.addEventListener('submit', (event) => {    event.preventDefault()    // Show error    if (hasInvalidInput()) {      formError()      inputList.forEach((inputElement) => {        checkInputValidity(inputElement)        toggleInputError(inputElement)      })      toggleInputError(checkboxElement)    }  })  inputList.forEach((inputElement) => {    inputElement.addEventListener('input', () => {      checkInputValidity(inputElement)      toggleButton()    })    inputElement.addEventListener('blur', () => {      toggleInputError(inputElement)    })    inputElement.addEventListener('focus', () => {      toggleErrorSpan(inputElement)    })  })  checkboxElement.addEventListener('change', () => {    toggleInputError(checkboxElement)    toggleButton()  })}function toggleButton() {  if (hasInvalidInput()) {    buttonElement.classList.add('button-inactive')    buttonElement.setAttribute('aria-disabled', 'true')  } else {    buttonElement.classList.remove('button-inactive')    buttonElement.setAttribute('aria-disabled', 'false')    // Remove error text    formErrorElement.textContent = ''  }}// Here we store and add text to the needed containerfunction formError() {  const errorMessage = 'Please fill in all fields to submit the form.'  formErrorElement.textContent = errorMessage}
          const formErrorElement = form.querySelector('.form__empty-error')

function startValidation() {
  form.addEventListener('submit', (event) => {
    event.preventDefault()
    // Show error
    if (hasInvalidInput()) {
      formError()
      inputList.forEach((inputElement) => {
        checkInputValidity(inputElement)
        toggleInputError(inputElement)
      })
      toggleInputError(checkboxElement)
    }
  })
  inputList.forEach((inputElement) => {
    inputElement.addEventListener('input', () => {
      checkInputValidity(inputElement)
      toggleButton()
    })
    inputElement.addEventListener('blur', () => {
      toggleInputError(inputElement)
    })
    inputElement.addEventListener('focus', () => {
      toggleErrorSpan(inputElement)
    })
  })
  checkboxElement.addEventListener('change', () => {
    toggleInputError(checkboxElement)
    toggleButton()
  })
}

function toggleButton() {
  if (hasInvalidInput()) {
    buttonElement.classList.add('button-inactive')
    buttonElement.setAttribute('aria-disabled', 'true')
  } else {
    buttonElement.classList.remove('button-inactive')
    buttonElement.setAttribute('aria-disabled', 'false')
    // Remove error text
    formErrorElement.textContent = ''
  }
}

// Here we store and add text to the needed container
function formError() {
  const errorMessage = 'Please fill in all fields to submit the form.'
  formErrorElement.textContent = errorMessage
}

        
        
          
        
      

Our code is ready! Don't forget:

  1. To call the function toggleButton() at the very beginning of the startValidation() function so that the button is already disabled before entering any characters.
  2. To invoke the startValidation() function.
        
          
          // Call the functionstartValidation()function startValidation() {  toggleButton()  form.addEventListener('submit', (event) => {    event.preventDefault()    if (hasInvalidInput()) {      formError()      inputList.forEach((inputElement) => {        checkInputValidity(inputElement)        toggleInputError(inputElement)      })      toggleInputError(checkboxElement)    }  })  inputList.forEach((inputElement) => {    inputElement.addEventListener('input', () => {      checkInputValidity(inputElement)      toggleButton()    })    inputElement.addEventListener('blur', () => {      toggleInputError(inputElement)    })    inputElement.addEventListener('focus', () => {      toggleErrorSpan(inputElement)    })  })  checkboxElement.addEventListener('change', () => {    toggleInputError(checkboxElement)    toggleButton()  })}
          // Call the function
startValidation()

function startValidation() {
  toggleButton()

  form.addEventListener('submit', (event) => {
    event.preventDefault()
    if (hasInvalidInput()) {
      formError()
      inputList.forEach((inputElement) => {
        checkInputValidity(inputElement)
        toggleInputError(inputElement)
      })
      toggleInputError(checkboxElement)
    }
  })

  inputList.forEach((inputElement) => {
    inputElement.addEventListener('input', () => {
      checkInputValidity(inputElement)
      toggleButton()
    })
    inputElement.addEventListener('blur', () => {
      toggleInputError(inputElement)
    })
    inputElement.addEventListener('focus', () => {
      toggleErrorSpan(inputElement)
    })
  })
  checkboxElement.addEventListener('change', () => {
    toggleInputError(checkboxElement)
    toggleButton()
  })
}

        
        
          
        
      
Open demo in the new window