Live Form Validation (Basic)

Lightweight live form validation for Webflow covering text inputs, email, and textarea fields. Includes anti-spam protection and a custom submit handler. No support for select, radio, or checkbox groups — use the Advanced version for those.

validationjavascriptwebflowemail

Code

HTML
html
<div data-form-validate="" class="form-group w-form">
  <form id="wf-form-Default-Form" name="wf-form-Default-Form" data-name="Default Form" method="get" class="form">
    <div data-validate="" class="form-field-group">
      <label for="name" class="form-label">Name <span class="form-required">*</span></label>
      <div class="form-field">
        <input class="form-input w-input" maxlength="256" name="name" data-name="Name" min="3" placeholder="Osmo" type="text" id="name" required="">
        <div class="form-field-icon is--success"><!-- success SVG --></div>
        <div class="form-field-icon is--error"><!-- error SVG --></div>
      </div>
    </div>
    <div data-validate="" class="form-field-group">
      <label for="email" class="form-label">Email Address <span class="form-required">*</span></label>
      <div class="form-field">
        <input class="form-input w-input" maxlength="256" name="email" data-name="Email" placeholder="hello@osmo.supply" type="email" id="email" required="">
        <div class="form-field-icon is--success"><!-- success SVG --></div>
        <div class="form-field-icon is--error"><!-- error SVG --></div>
      </div>
    </div>
    <div for="message" data-validate="" class="form-field-group">
      <label for="message" class="form-label">Message <span class="form-required">*</span></label>
      <div class="form-field">
        <textarea class="form-input is--textarea w-input" maxlength="5000" name="message" data-name="Message" min="3" placeholder="Hello Osmo, " id="message" required=""></textarea>
        <div class="form-field-icon is--success"><!-- success SVG --></div>
        <div class="form-field-icon is--error"><!-- error SVG --></div>
      </div>
    </div>
    <div class="form-field-group">
      <div class="form-divider"></div>
    </div>
    <div class="form-field-group">
      <div class="form-field">
        <div data-submit="" tabindex="0" class="form-submit-btn">
          <p class="form-submit-btn-p">Submit</p>
          <input type="submit" data-wait="Please wait..." class="form-submit w-button" value="Submit">
        </div>
      </div>
    </div>
  </form>
  <div class="form-notifcation w-form-done">
    <div class="form-notification-bg"></div>
    <div class="form-notification-p">Success! We'll be in touch soon.</div>
    <div class="form-notification-icon"><!-- success SVG --></div>
  </div>
  <div class="form-notifcation is--error w-form-fail">
    <div class="form-notification-bg"></div>
    <div class="form-notification-p">Something went wrong while submitting.</div>
    <div class="form-notification-icon"><!-- error SVG --></div>
  </div>
</div>
CSS
css
.form-group {
  grid-column-gap: 1.5em;
  grid-row-gap: 1.5em;
  flex-flow: column;
  width: 25em;
  margin-bottom: 0;
  display: flex;
}

.form {
  grid-column-gap: 1.5em;
  grid-row-gap: 1.5em;
  flex-flow: column;
  width: 100%;
  display: flex;
}

.form-field-group {
  grid-column-gap: .75em;
  grid-row-gap: .75em;
  flex-flow: column;
  align-items: flex-start;
  display: flex;
}

.form-field {
  width: 100%;
  position: relative;
}

.form-label {
  color: #131313;
  width: 100%;
  margin-bottom: 0;
  font-size: .875em;
  font-weight: 500;
  line-height: 1;
}

.form-required {
  color: #ff4c24;
}

.form-input {
  outline-offset: 0px;
  color: #131313;
  -webkit-appearance: none;
  appearance: none;
  box-sizing: border-box;
  vertical-align: middle;
  background-color: #efeeec;
  border: 1px solid #efeeec;
  border-radius: .328125em;
  outline: 0 #0000;
  height: auto;
  margin-bottom: 0;
  padding: .9em 3.5em .9em 1em;
  font-size: 1.125em;
  font-weight: 500;
  line-height: 1.2;
  box-shadow: 0 0 #0000;
}

.form-input.is--textarea {
  resize: vertical;
  min-height: 9em;
}

.form-input:focus {
  border-color: #cbc8c5;
}

.form-input::placeholder {
  color: #1313134d;
  background-color: #efeeec;
}

.form-field-icon {
  opacity: 0;
  pointer-events: none;
  color: #cbc8c5;
  -webkit-user-select: none;
  user-select: none;
  border-radius: .375em;
  justify-content: center;
  align-items: center;
  width: 3.5em;
  max-height: 3.5em;
  padding-left: 1em;
  padding-right: 1em;
  display: flex;
  position: absolute;
  top: 1px;
  bottom: 1px;
  right: 1px;
}

.form-field-icon.is--error {
  color: #ff4c24;
}

.form-submit {
  visibility: hidden;
  opacity: 0;
  position: absolute;
  inset: 0;
}

.form-submit-btn {
  outline-offset: 0px;
  color: #efeeec;
  cursor: pointer;
  background-color: #131313;
  border: 1px solid #131313;
  border-radius: .375rem;
  outline: 0 #0000;
  flex-flow: row;
  justify-content: flex-start;
  align-items: center;
  padding: 1.005em 1.125em;
  display: flex;
  position: relative;
  overflow: hidden;
  box-shadow: inset 0 0 #0000;
}

.form-submit-btn:focus {
  outline-offset: 0px;
  border-width: 1px;
  border-color: #cbc8c5;
  outline-color: #131313;
}

.form-submit-btn-p {
  margin-bottom: 0;
  font-size: 1.125em;
  font-weight: 500;
  line-height: 1.2;
}

.form-divider {
  opacity: .15;
  background-color: #131313;
  width: 100%;
  height: 1px;
}

.form-notifcation {
  outline-offset: 0px;
  color: #0ba954;
  text-align: left;
  border: 1px solid #0ba954;
  border-color: inherit;
  background-color: #efeeec;
  border-radius: .375rem;
  outline: 0 #0000;
  width: 100%;
  padding: 1.125em;
  font-size: 1em;
  position: relative;
}

.form-notifcation.is--error {
  color: #ff4c24;
  margin-top: 0;
  padding-right: 3.5em;
}

.form-notification-icon {
  pointer-events: none;
  color: inherit;
  justify-content: center;
  align-items: center;
  width: 3.5em;
  padding-left: 1em;
  padding-right: 1em;
  display: flex;
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
}

.form-notification-bg {
  opacity: .1;
  pointer-events: none;
  color: inherit;
  background-color: currentColor;
  border-radius: calc(.375rem - 2px);
  display: flex;
  position: absolute;
  inset: 0;
}

.form-notification-p {
  color: inherit;
  font-size: 1.125em;
  font-weight: 500;
}

@media screen and (max-width: 767px) {
  .form-group {
    width: 100%;
  }
}

/* Field: Error */
[data-validate].is--error input,
[data-validate].is--error textarea {
  border-color: #FF4C24;
}

[data-validate].is--error .form-field-icon.is--error {
  opacity: 1;
}

/* Field: Success */
[data-validate].is--success .form-field-icon.is--success {
  opacity: 1;
}
JavaScript
javascript
function initBasicFormValidation() {
  const forms = document.querySelectorAll('[data-form-validate]');

  forms.forEach((form) => {
    const fields = form.querySelectorAll('[data-validate] input, [data-validate] textarea');
    const submitButtonDiv = form.querySelector('[data-submit]');
    const submitInput = submitButtonDiv.querySelector('input[type="submit"]');

    const formLoadTime = new Date().getTime();

    const validateField = (field) => {
      const parent = field.closest('[data-validate]');
      const minLength = field.getAttribute('min');
      const maxLength = field.getAttribute('max');
      const type = field.getAttribute('type');
      let isValid = true;

      if (field.value.trim() !== '') {
        parent.classList.add('is--filled');
      } else {
        parent.classList.remove('is--filled');
      }

      if (minLength && field.value.length < minLength) {
        isValid = false;
      }

      if (maxLength && field.value.length > maxLength) {
        isValid = false;
      }

      if (type === 'email' && !/\S+@\S+\.\S+/.test(field.value)) {
        isValid = false;
      }

      if (isValid) {
        parent.classList.remove('is--error');
        parent.classList.add('is--success');
      } else {
        parent.classList.remove('is--success');
        parent.classList.add('is--error');
      }

      return isValid;
    };

    const startLiveValidation = (field) => {
      field.addEventListener('input', function () {
        validateField(field);
      });
    };

    const validateAndStartLiveValidationForAll = () => {
      let allValid = true;
      let firstInvalidField = null;

      fields.forEach((field) => {
        const valid = validateField(field);
        if (!valid && !firstInvalidField) {
          firstInvalidField = field;
        }
        if (!valid) {
          allValid = false;
        }
        startLiveValidation(field);
      });

      if (firstInvalidField) {
        firstInvalidField.focus();
      }

      return allValid;
    };

    const isSpam = () => {
      const currentTime = new Date().getTime();
      const timeDifference = (currentTime - formLoadTime) / 1000;
      return timeDifference < 5;
    };

    submitButtonDiv.addEventListener('click', function () {
      if (validateAndStartLiveValidationForAll()) {
        if (isSpam()) {
          alert('Form submitted too quickly. Please try again.');
          return;
        }
        submitInput.click();
      }
    });

    form.addEventListener('keydown', function (event) {
      if (event.key === 'Enter' && event.target.tagName !== 'TEXTAREA') {
        event.preventDefault();
        if (validateAndStartLiveValidationForAll()) {
          if (isSpam()) {
            alert('Form submitted too quickly. Please try again.');
            return;
          }
          submitInput.click();
        }
      }
    });
  });
}

// Initialize Basic Form Validation
document.addEventListener('DOMContentLoaded', () => {
  initBasicFormValidation();
});

Attributes

NameTypeDefaultDescription
data-form-validateattributeAdd to the outer container (parent of the <form> element). Signals the script where to find the form.
data-validateattributeAdd to each field group (label + input/textarea). Required on every group that needs validation.
data-submitattributeWrap the submit button. The script intercepts the click, validates all fields, then triggers the real submit only if valid.
minnumberMinimum character length required for the field to pass validation.
maxnumberMaximum character length allowed for the field.

Notes

  • Only supports <input> and <textarea> fields — no select, radio, or checkbox validation.
  • Live validation starts on each field only after the user first interacts (input event). On submit, all fields are validated at once.
  • Anti-spam: submission is blocked if the form is submitted in less than 5 seconds after page load.
  • Pressing Enter in any input (except <textarea>) triggers the full validation and custom submit logic.
  • Dynamic classes: .is--error (fails validation), .is--success (passes validation), .is--filled (field has a value).
  • Need select, radio, or checkbox support? Use Live Form Validation (Advanced) instead.