JSFiddle - React, Tailwind, and code Playground
by Alvaro Montoro
HTML
<form method="post" id="login-form" class="login-form" autocomplete="off">
<h1 class="a11y-hidden">Login form</h1>
<figure aria-hidden="true">
<div class="person-body"></div>
<div class="neck skin"></div>
<div class="head skin">
<div class="eyes"></div>
<div class="mouth"></div>
</div>
<div class="hair"></div>
<div class="ears"></div>
<div class="shirt-1"></div>
<div class="shirt-2"></div>
</figure>
<div aria-live="polite" class="error-message" id="error-message"></div>
<div>
<label class="required">
<span>Username</span>
<input type="text" name="username" id="username" placeholder="Username" required />
</label>
</div>
<div>
<label class="required">
<span>Password</span>
<input type="password" name="password" id="password" placeholder="Password" required minlength="8" />
</label>
</div>
<div>
<label>
<input type="checkbox" name="show-password" id="show-password" class="a11y-hidden" />
<span class="checkbox-label">Show Password</span>
</label>
</div>
<input type="submit" value="Log In" name="submit" id="submit" />
<div class="forgot-password">
<a href="#">Forgot password?</a>
</div>
</form>
CSS
html, body {
align-items: center;
background: #f2f4f8;
border: 0;
display: flex;
font-family: Helvetica, Arial, sans-serif;
font-size: 16px;
height: 100%;
justify-content: center;
margin: 0;
padding: 0;
}
.a11y-hidden {
position: absolute;
left: -1000em;
}
form {
--background: rgba(255, 255, 255, 1);
--border: rgba(0, 0, 0, 0.125);
--borderDark: rgba(0, 0, 0, 0.25);
--borderDarker: rgba(0, 0, 0, 0.5);
--borderRadius: 0.125rem;
--bgColorH: 0;
--bgColorS: 0%;
--bgColorL: 98%;
--fgColorH: 210;
--fgColorS: 50%;
--fgColorL: 38%;
--errorMessage: #dd0000;
--errorBackground: #fff5f5;
background: var(--background);
border: 1px solid var(--border);
border-radius: var(--borderRadius);
box-shadow: 0 1rem 1rem -0.75rem var(--border);
padding: 1rem;
}
.error-message {
background: var(--errorBackground);
border: 1px solid var(--errorMessage);
border-radius: var(--borderRadius);
color: var(--errorMessage);
font-size: 0.825rem;
padding: 0.25rem 0.5rem;
}
.error-message:empty {
display: none;
}
input {
border: 1px solid var(--border);
border-radius: var(--borderRadius);
box-sizing: border-box;
font-size: 1rem;
height: 2.25rem;
line-height: 1.25rem;
margin-top: 0.25rem;
outline: 1px dashed transparent;
outline-offset: 2px;
padding: 0.25rem 0.5rem;
transition: all 0.25s;
width: 15rem;
}
input:focus {
outline: 1px dashed hsl(var(--fgColorH), calc(var(--fgColorS) * 2), calc(var(--fgColorL) * 1.15));
}
label {
display: inline-block;
}
label span {
color: var(--borderDarker);
display: block;
font-size: 0.825rem;
margin-top: 1rem;
transition: color 0.25s;
}
label.required span::after {
content: "*";
color: var(--errorMessage);
margin-left: 0.125rem;
}
label:hover span {
color: hsl(var(--fgColorH), var(--fgColorS), var(--fgColorL));
}
label:hover input,
label:hover span.checkbox-label::before {
border-color: hsl(var(--fgColorH), var(--fgColorS),...
JavaScript
// show password interactivity
document.querySelector("#show-password").addEventListener("input", function() {
document.querySelector("#password").type = this.checked ? "text" : "password";
document.querySelector(".eyes").className = `eyes ${this.checked && " closed"}`;
});
// focus within the label input
const labels = document.querySelectorAll("#login-form label input");
for (let x = 0; x < labels.length; x++) {
labels[x].addEventListener("focus", function() {
this.parentNode.classList.add("state-focus");
});
labels[x].addEventListener("blur", function() {
this.parentNode.classList.remove("state-focus");
});
}
// form submission = validation
document.querySelector(".login-form").addEventListener("submit", function(e) {
let errorMessage = "";
const error = document.querySelector("#error-message");
const username = document.querySelector("#username");
const password = document.querySelector("#password");
error.textContent = "";
// check password length
if (password.value.length < 8) {
errorMessage = "Password is mandatory";
password.parentNode.classList.add("state-error");
password.focus();
// check email length
} else if (username.value.length < 5) {
errorMessage = "Username is too short (min 5 chars)";
username.parentNode.classList.add("state-error");
username.focus();
}
if (errorMessage != "") {
error.textContent = errorMessage;
e.preventDefault();
}
// prevent form submission anyway for demo purposes
e.preventDefault();
});
// remove state error on blur
function removeError(e) {
e.target.parentNode.classList.remove("state-error");
document.querySelector("#error-message").textContent = "";
}
function checkStatus(e) {
removeError(e);
const invalidFields = document.querySelectorAll("input:invalid").length;
document.querySelector(".mouth").className = `mouth errors-${invalidFields}`;
}
document.querySelector("#password").addEventListener("input",...