Form Instant Validation
A registration form with real-time status, error messages, and submission feedback.
With JavaScript enabled, you can edit, run, and save this example. The fixed source is as follows:
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Form Instant Validation</title>
<style>
* { box-sizing: border-box; }
body { min-height: 100vh; margin: 0; display: grid; place-items: center; padding: 24px; color: #23252b; font-family: "Avenir Next", "Microsoft YaHei", sans-serif; background: #f3e7da; }
.shell { width: min(920px, 100%); display: grid; grid-template-columns: .86fr 1.14fr; overflow: hidden; border: 1px solid #d6c7b8; border-radius: 28px; background: #fffdf9; box-shadow: 0 32px 80px rgba(73,52,34,.14); }
.aside { position: relative; min-height: 620px; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden; padding: clamp(28px, 5vw, 48px); color: #fff; background: #28483f; }
.aside::before { content: ""; position: absolute; width: 330px; height: 330px; top: 110px; left: -120px; border: 70px solid rgba(246,180,100,.22); border-radius: 50%; }
.logo, .quote { position: relative; z-index: 1; }
.logo { font: 800 18px/1 Georgia, serif; }
.logo i { color: #f6b464; font-style: normal; }
.quote strong { display: block; font: 500 clamp(30px, 5vw, 46px)/1.1 Georgia, "STSong", serif; }
.quote p { color: #b9c9c4; line-height: 1.7; }
.form-wrap { padding: clamp(28px, 6vw, 58px); }
.eyebrow { color: #c76343; font-size: 11px; font-weight: 900; letter-spacing: .18em; }
h1 { margin: 9px 0 8px; font: 700 clamp(34px, 6vw, 52px)/1 Georgia, "STSong", serif; }
.intro { margin: 0 0 30px; color: #7b7d82; font-size: 14px; }
.field { margin-bottom: 18px; }
label { display: block; margin-bottom: 7px; color: #4a4c52; font-size: 12px; font-weight: 800; }
input[type="text"], input[type="email"], input[type="password"] { width: 100%; padding: 13px 14px; border: 1px solid #d8d7d2; border-radius: 11px; color: #24262b; font: inherit; background: #fff; transition: border-color .18s, box-shadow .18s; }
input:focus { border-color: #3c7566; outline: 0; box-shadow: 0 0 0 3px rgba(60,117,102,.12); }
.field.is-invalid input { border-color: #c44f3f; box-shadow: 0 0 0 3px rgba(196,79,63,.1); }
.field.is-valid input { border-color: #3c7566; }
.error { min-height: 17px; margin: 5px 2px 0; color: #c44f3f; font-size: 11px; }
.password-meta { display: flex; gap: 5px; margin-top: 8px; }
.bar { flex: 1; height: 3px; border-radius: 3px; background: #e4e1db; }
.bar.active { background: #3c7566; }
.terms { display: flex; align-items: flex-start; gap: 9px; margin: 4px 0 22px; color: #6c6e73; font-size: 12px; line-height: 1.5; }
.terms input { width: 17px; height: 17px; margin: 1px 0 0; accent-color: #3c7566; }
.submit { width: 100%; padding: 14px 18px; border: 0; border-radius: 11px; color: #fff; font: inherit; font-weight: 850; background: #c76343; box-shadow: 0 7px 0 #8e402c; cursor: pointer; }
.submit:active { transform: translateY(4px); box-shadow: 0 3px 0 #8e402c; }
.submit:focus-visible { outline: 3px solid #28483f; outline-offset: 3px; }
.result { min-height: 20px; margin: 18px 0 0; color: #28705b; font-size: 13px; font-weight: 800; text-align: center; }
@media (max-width: 720px) { .shell { grid-template-columns: 1fr; } .aside { min-height: 240px; } .quote p { display: none; } }
@media (prefers-reduced-motion: reduce) { input { transition: none !important; } }
</style>
</head>
<body>
<main class="shell">
<aside class="aside"><div class="logo">Field<i>Note</i></div><div class="quote"><strong>Turn inspiration into an actionable plan.</strong><p>A quiet space to record, organize, and share your next step.</p></div></aside>
<section class="form-wrap"><span class="eyebrow">CREATE ACCOUNT</span><h1>Create New Account</h1><p class="intro">All validations are completed on the current page, and no data will be submitted.</p>
<form id="signup" novalidate>
<div class="field"><label for="name">Nickname</label><input id="name" name="name" type="text" autocomplete="name" maxlength="24" aria-describedby="nameError"><p id="nameError" class="error"></p></div>
<div class="field"><label for="email">Email Address</label><input id="email" name="email" type="email" autocomplete="email" aria-describedby="emailError"><p id="emailError" class="error"></p></div>
<div class="field"><label for="password">Password</label><input id="password" name="password" type="password" autocomplete="new-password" aria-describedby="passwordError"><div class="password-meta" aria-hidden="true"><i class="bar"></i><i class="bar"></i><i class="bar"></i></div><p id="passwordError" class="error"></p></div>
<label class="terms"><input id="terms" type="checkbox"><span>I have read and agree to the sample terms, and understand that this is only a local demo form.</span></label>
<button class="submit" type="submit">Registration complete</button><p id="result" class="result" role="status" aria-live="polite"></p>
</form>
</section>
</main>
<script>
const form = document.querySelector('#signup');
const fields = {
name: { input: document.querySelector('#name'), check: value => value.trim().length >= 2 ? '' : 'Nickname must be at least 2 characters long.' },
email: { input: document.querySelector('#email'), check: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? '' : 'Please enter a valid email address.' },
password: { input: document.querySelector('#password'), check: value => value.length >= 8 && /[A-Za-z]/.test(value) && /\d/.test(value) ? '' : 'The password must be at least 8 characters long and include both letters and numbers.' }
};
function validateField(key) {
const field = fields[key];
const message = field.check(field.input.value);
const wrapper = field.input.closest('.field');
wrapper.classList.toggle('is-invalid', Boolean(message));
wrapper.classList.toggle('is-valid', !message);
field.input.setAttribute('aria-invalid', String(Boolean(message)));
document.querySelector(`#${key}Error`).textContent = message;
return !message;
}
Object.entries(fields).forEach(([key, field]) => {
field.input.addEventListener('blur', () => validateField(key));
field.input.addEventListener('input', () => { if (field.input.closest('.field').classList.contains('is-invalid')) validateField(key); if (key === 'password') updateStrength(); });
});
function updateStrength() {
const value = fields.password.input.value;
const score = [value.length >= 8, /[A-Za-z]/.test(value) && /\d/.test(value), /[^A-Za-z0-9]/.test(value)].filter(Boolean).length;
document.querySelectorAll('.bar').forEach((bar, index) => bar.classList.toggle('active', index < score));
}
form.addEventListener('submit', event => {
event.preventDefault();
const valid = Object.keys(fields).map(validateField).every(Boolean);
const result = document.querySelector('#result');
if (!document.querySelector('#terms').checked) { result.textContent = 'Please check the consent example terms first.'; return; }
result.textContent = valid ? 'Validation passed! This example will not actually create an account.' : 'Please check the input fields highlighted in red.';
if (!valid) document.querySelector('[aria-invalid="true"]')?.focus();
});
</script>
</body>
</html>