<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()
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.