<div class="container">
  <h2 class="title">Create a new account</h2>
  <form action="#" class="form">

    <div class="input-group">
      <label for="username" class="label">Username</label>
      <input id="username" placeholder="John Doe" type="text" class="input">
      <span class="error-message"></span>
      <svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <title>check-circle</title>
        <g fill="none">
          <path d="M9 12l2 2 4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
        </g>
      </svg>

      <svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <title>exclamation-circle</title>
        <g fill="none">
          <path d="M12 8v4m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
        </g>
      </svg>
    </div>

    <div class="input-group">
      <label for="email" class="label">Email</label>
      <input id="email" type="email" class="input" autocomplete placeholder="john.doe@example.com">
      <span class="error-message"></span>
      <svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <title>check-circle</title>
        <g fill="none">
          <path d="M9 12l2 2 4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
        </g>
      </svg>

      <svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <title>exclamation-circle</title>
        <g fill="none">
          <path d="M12 8v4m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
        </g>
      </svg>
    </div>

    <div class="input-group">
      <label for="password" class="label">Password</label>
      <input id="password" type="password" class="input">
      <span class="error-message"></span>
      <svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <title>check-circle</title>
        <g fill="none">
          <path d="M9 12l2 2 4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
        </g>
      </svg>

      <svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <title>exclamation-circle</title>
        <g fill="none">
          <path d="M12 8v4m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
        </g>
      </svg>
    </div>

    <div class="input-group">
      <label for="password_confirmation" class="label">Password Confirmation</label>
      <input id="password_confirmation" type="password" class="input">
      <span class="error-message"></span>
      <svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <title>check-circle</title>
        <g fill="none">
          <path d="M9 12l2 2 4-4m6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
        </g>
      </svg>

      <svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <title>exclamation-circle</title>
        <g fill="none">
          <path d="M12 8v4m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
        </g>
      </svg>
    </div>

    <input type="submit" class="button" value="Create account">
  </form>
</div>
* {
  box-sizing: border-box;
}

body {
  font-family: "Inter", sans-serif;
  background-color: teal;
}

.title {
  margin-bottom: 2rem;
}

.hidden {
  display: none;
}

.icon {
  width: 24px;
  height: 24px;
  position: absolute;
  top: 32px;
  right: 5px;
  pointer-events: none;
  z-index: 2;
}

.icon.icon-success {
  stroke: teal;
}

.icon.icon-error {
  stroke: red;
}

.container {
  max-width: 460px;
  margin: 40px auto;
  padding: 40px;
  border: 1px solid #ddd;
  border-radius: 10px;
  background-color: white;
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
    0 10px 10px -5px rgba(0, 0, 0, 0.04);
}

.label {
  font-weight: bold;
  display: block;
  color: #333;
  margin-bottom: 0.25rem;
  color: #2d3748;
}

.input {
  appearance: none;
  display: block;
  width: 100%;
  color: #2d3748;
  border: 1px solid #cbd5e0;
  line-height: 1.25;
  background-color: white;
  padding: 0.65rem 0.75rem;
  border-radius: 0.25rem;
}

.input::placeholder {
  color: #a0aec0;
}

.input.input-error {
  border: 1px solid red;
}

.input.input-error:focus {
  border: 1px solid red;
}

.input:focus {
  outline: none;
  border: 1px solid #a0aec0;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
  background-clip: padding-box;
}

.input-group {
  margin-bottom: 2rem;
  position: relative;
}

.error-message {
  font-size: 0.85rem;
  color: red;
}

.button {
  background-color: teal;
  padding: 1rem 2rem;
  border: none;
  border-radius: 0.25rem;
  color: white;
  font-weight: bold;
  display: block;
  width: 100%;
  text-align: center;
  cursor: pointer;
}

.button:hover {
  filter: brightness(110%);
}
class FormValidator {
  constructor(form, fields) {
    this.form = form
    this.fields = fields
  }

  initialize() {
    this.validateOnEntry()
    this.validateOnSubmit()
  }

  validateOnSubmit() {
    let self = this

    this.form.addEventListener('submit', event => {
        event.preventDefault()
        self.fields.forEach(field => {
        const input = document.querySelector(`#${field}`)
        self.validateFields(input)
      })
    })
  }

  validateOnEntry() {
    let self = this
    this.fields.forEach(field => {
      const input = document.querySelector(`#${field}`)

      input.addEventListener('input', () => {
        self.validateFields(input)
      })
    })
  }

  validateFields(field) {

    // Check presence of values
    if (field.value.trim() === "") {
      this.setStatus(field, `${field.previousElementSibling.innerText} cannot be blank`, "error")
    } else {
      this.setStatus(field, null, "success")
    }

    // check for a valid email address
    if (field.type === "email") {
      const re = /\S+@\S+\.\S+/
      if (re.test(field.value)) {
        this.setStatus(field, null, "success")
      } else {
        this.setStatus(field, "Please enter valid email address", "error")
      }
    }

    // Password confirmation edge case
    if (field.id === "password_confirmation") {
      const passwordField = this.form.querySelector('#password')

      if (field.value.trim() == "") {
        this.setStatus(field, "Password confirmation required", "error")
      } else if (field.value != passwordField.value) {
        this.setStatus(field, "Password does not match", "error")
      } else {
        this.setStatus(field, null, "success")
      }
    }
  }

  setStatus(field, message, status) {
    const successIcon = field.parentElement.querySelector('.icon-success')
    const errorIcon = field.parentElement.querySelector('.icon-error')
    const errorMessage = field.parentElement.querySelector('.error-message')

    if (status === "success") {
      if (errorIcon) { 
        errorIcon.classList.add('hidden') 
      }
      
      if (errorMessage) { 
        errorMessage.innerText = "" 
      }
      successIcon.classList.remove('hidden')
      field.classList.remove('input-error')
    }

    if (status === "error") {
      if (successIcon) { 
        successIcon.classList.add('hidden') 
      }
      
      field.parentElement.querySelector('.error-message').innerText = message
      errorIcon.classList.remove('hidden')
      field.classList.add('input-error')
    }
  }
}

const form = document.querySelector('.form')
const fields = ["username", "email", "password", "password_confirmation"]

const validator = new FormValidator(form, fields)
validator.initialize()

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.