The JavaScript way to handle this would be to use the constraint validation API to check each field as the user interacts with it, then again on form submit.
Use a keyUp
event listener to watch each field for changes to check for their validity, then a submit
event listener to check the value of each field again before sending. Below I've created an example shamelessly lifted and only lightly edited from the linked MDN page.
Your form might look something like this:
<!-- form disables HTML validation -->
<form novalidate>
<label for='submitthingy'>
<!-- input field and error span are in the same label -->
<input id='submittable' name='submitthingy' required>
<!-- error to be called by JavaScript -->
<span class='error' aria-live='polite'></span>
</label>
<button type='submit'>Click Me</button>
</form>
And your JS like this:
var form = document.getElementsByTagName('form')[0];
var submitthingy = document.getElementById('submittable');
var error = document.querySelector('.error');
// One of these for each field, or you could DRY it up somehow
email.addEventListener("keyup", function (event) {
if (submitthingy.validity.valid) {
error.innerHTML = "";
error.className = "error";
}
}, false);
form.addEventListener("submit", function (event) {
if (!submitthingy.validity.valid) {
error.innerHTML = "This field is not valid per CSS pseudo class!";
error.className = "error active";
event.preventDefault();
}
}, false);
Then you can style your error messages using CSS to hide them by default, then apply styles based on the class(es) error active
.