<form action="" x-data="form" @submit="submit">
<h1>Log In</h1>
<label for="username">Username</label>
<input name="username" id="username" type="text" x-bind:class="{'invalid':username.errorMessage && username.blurred}" data-rules='["required"]' @blur="blur" @input="input">
<p x-show="username.errorMessage && username.blurred" x-text="username.errorMessage"></p>
<label for="email">Email</label>
<input name="email" type="email" id="email" x-bind:class="{'invalid':email.errorMessage && email.blurred}" data-rules='["required","email"]' @blur="blur" @input="input">
<p x-show="email.errorMessage && email.blurred" x-text="email.errorMessage"></p>
<label for="password">Password</label>
<input name="password" type="password" id="password" x-bind:class="{'invalid':password.errorMessage && password.blurred}" data-rules='["required","minimum:8"]' @blur="blur" @input="input">
<p x-show="password.errorMessage && password.blurred" x-text="password.errorMessage"></p>
<label for="passwordConf">Confirm Password</label>
<input name="passwordConf" type="password" id="passwordConf" x-bind:class="{'invalid':passwordConf.errorMessage && passwordConf.blurred}" data-rules='["required","minimum:8"]' @blur="blur" @input="input">
<p x-show="passwordConf.errorMessage && passwordConf.blurred" x-text="passwordConf.errorMessage"></p>
<input type="submit">
</form>
* {
font-family: "Inter", sans-serif;
}
form {
width: 30%;
min-width: 350px;
margin: auto;
}
label {
font-size: 1.5em;
}
input {
width: 100%;
display: block;
font-size: 2em;
border: solid 4px;
border-color: hsl(210, 100%, 30%);
margin-bottom: 1.5em;
}
input[type="submit"] {
width: fit-content;
font-size: 1.5em;
}
input[type="submit"]:focus {
background-color: hsl(210, 100%, 80%);
}
.invalid {
border-color: darkred;
background-color: hsl(0, 30%, 95%);
margin-bottom: 0em;
}
.error-message {
margin-bottom: 1em;
color: hsl(0deg, 100%, 15%);
}
import Alpine from "https://cdn.skypack.dev/alpinejs";
import kingshottIodine from "https://cdn.skypack.dev/@kingshott/iodine";
Alpine.data("form", form);
Alpine.start();
function form() {
return {
username: { errorMessage: "", blurred: false },
email: { errorMessage: "", blurred: false },
password: { errorMessage: "", blurred: false },
passwordConf: { errorMessage: "", blurred: false },
blur: function (event) {
let ele = event.target;
this[ele.name].blurred = true;
let rules = JSON.parse(ele.dataset.rules);
this[ele.name].errorMessage = this.getErrorMessage(ele.value, rules);
},
input: function (event) {
let ele = event.target;
let rules = JSON.parse(ele.dataset.rules);
this[ele.name].errorMessage = this.getErrorMessage(ele.value, rules);
},
submit: function (event) {
let inputs = [...this.$el.querySelectorAll("input[data-rules]")];
inputs.map((input) => {
if (Iodine.is(input.value, JSON.parse(input.dataset.rules)) !== true) {
event.preventDefault();
}
});
},
getErrorMessage: function (value, rules) {
let isValid = Iodine.is(value, rules);
if (isValid !== true) {
return Iodine.getErrorMessage(isValid);
}
return "";
}
};
}
This Pen doesn't use any external JavaScript resources.