JavaScript form validation is one of the most useful front-end techniques for building better website forms. It helps check user input before a form is submitted, show clear error messages, guide visitors through required fields, validate email addresses, confirm passwords, improve checkout forms, and make signup, contact, login, booking, quote request, and payment forms easier to complete.
In this guide, you will find 30 JavaScript form validation examples for modern websites, including input validation, email validation, password validation, signup form validation, login form validation, checkout validation, contact form validation, file upload validation, phone number validation, required field validation, real-time validation, inline error messages, success states, multi-step form validation, and responsive HTML, CSS, and JavaScript validation patterns you can customize for your own projects.
This post focuses on JavaScript validation logic and user feedback, not only form styling. If you need visual form layouts, you can also explore our modern CSS forms guide. If you are building interactive UI components around your forms, you may also find our JavaScript modal examples, modern CSS buttons, and modern CSS tabs useful.
Table of Contents
- What Is JavaScript Form Validation?
- Why Form Validation Matters
- Client-Side vs Server-Side Validation
- What Should Good Form Validation Include?
- 30 JavaScript Form Validation Examples
- JavaScript Form Validation Best Practices
- Responsive Validation Design Tips
- Common Form Validation Mistakes
- JavaScript Form Validation FAQ
- Conclusion
- Related JavaScript and CSS Guides
What Is JavaScript Form Validation?
JavaScript form validation is the process of checking form input in the browser before the data is submitted. It can verify required fields, email format, password strength, matching passwords, phone number patterns, minimum and maximum character limits, checkbox acceptance, file type rules, date selection, payment details, and other user input conditions.
The main goal of JavaScript validation is to help users correct mistakes faster. Instead of submitting a form and waiting for a server response, visitors can see instant feedback near the field that needs attention. This makes contact forms, signup forms, login forms, checkout forms, booking forms, quote request forms, newsletter forms, and account forms feel smoother and easier to use.
Good validation does not only block bad input. It explains what is wrong, shows how to fix it, highlights the correct field, and confirms when the input is valid. The best JavaScript form validation examples combine clean HTML, polished CSS, useful error messages, accessible field states, and lightweight JavaScript that is easy to reuse across different website projects.
Why Form Validation Matters
Form validation matters because forms are often where visitors become leads, customers, subscribers, members, or users. A confusing form can stop someone from completing a signup, sending a message, requesting a quote, creating an account, or finishing checkout. Clear JavaScript validation can reduce friction by showing problems before the visitor gives up.
- It improves user experience by showing helpful feedback before a form is submitted.
- It reduces form errors because visitors can fix missing fields, invalid emails, weak passwords, and incorrect formats immediately.
- It supports conversions on signup forms, contact forms, newsletter forms, lead forms, quote request forms, and checkout pages.
- It saves time because users do not need to reload the page or wait for a server response for simple mistakes.
- It improves form clarity by showing required fields, success states, warning messages, and field-specific instructions.
JavaScript validation works especially well with strong form design. A well-designed input field should be readable, correctly spaced, easy to tap on mobile, and paired with a helpful label. For visual form layouts, see our modern CSS forms examples. For buttons inside forms, checkout steps, and submit actions, see our modern CSS buttons guide.
Client-Side vs Server-Side Validation
Client-side validation runs in the browser with HTML and JavaScript. It is useful for instant feedback, required fields, email format checks, password rules, character limits, checkbox validation, and other simple front-end checks. This is the type of validation used in the examples in this guide.
Server-side validation runs after the form is submitted to your backend, CMS, plugin, CRM, ecommerce system, or payment processor. It is required for security because users can disable JavaScript, modify front-end code, bypass browser checks, or send requests directly to the server. JavaScript validation improves usability, but it should not be your only protection for real forms.
- Use JavaScript validation for instant feedback such as missing fields, invalid email format, weak passwords, and live input messages.
- Use server-side validation for security because important rules must be checked again after submission.
- Use both together for the best result: a smoother user experience on the front end and safer data handling on the back end.
- Never trust front-end validation alone for payments, account creation, file uploads, checkout forms, user data, or private information.
What Should Good Form Validation Include?
A good JavaScript form validation system should make the form easier to complete, not harder. It should show clear labels, mark required fields, validate at the right time, display useful error messages, highlight the field that needs attention, confirm valid input, and avoid confusing users with vague or aggressive messages.
Clear field rules
Explain what each field needs, such as required input, email format, password length, file type, or checkbox acceptance.
Helpful error messages
Show specific messages near the field so users know exactly what to fix before submitting the form.
Accessible feedback
Use readable text, visible focus states, semantic labels, and clear success or error states for better usability.
Server-side backup
Use JavaScript for speed and feedback, but always validate important form data again on the server.
Before writing validation code, decide what each form should actually check. A newsletter form may only need a valid email address. A signup form may need name, email, password strength, and password confirmation. A checkout form may need billing details, shipping fields, card information, terms acceptance, and address validation. A file upload form may need file size and file type checks. Each form should validate only what matters for that specific user journey.
- Validate required fields so users know which information must be completed.
- Validate email fields to catch missing symbols, incomplete domains, or obvious typing mistakes.
- Validate passwords carefully with clear rules for length, numbers, symbols, uppercase letters, or confirmation matching.
- Validate checkboxes when users must accept terms, privacy policies, subscriptions, or checkout conditions.
- Validate files when upload forms need specific file types, file sizes, or required attachments.
- Show errors near the field instead of only showing one generic message at the top of the form.
- Keep messages human because “Please enter a valid email address” is better than “Invalid input”.
- Do not rely only on color because some users may not see red and green states clearly.
- Use server validation too because front-end validation can be bypassed.
You can combine JavaScript form validation with many other UI components. A signup form can appear inside a JavaScript modal, a checkout form can use pricing table CTAs, a multi-step validation flow can use ideas from modern CSS tabs, and validation messages can be styled together with modern CSS forms.
30 JavaScript Form Validation Examples
Now let’s look at 30 JavaScript form validation examples for real website projects. Each example uses a different validation pattern, form type, message style, or interaction flow, so you can build required field validation, email validation, password validation, signup validation, login validation, checkout validation, contact form validation, file upload validation, phone number validation, multi-step validation, live validation, and responsive website forms with complete HTML, CSS, and JavaScript code.
1. Real-Time Signup Form Validation
Real-time signup form validation helps users fix mistakes while they are typing instead of waiting until the form is submitted. This pattern is useful for account registration forms, SaaS signup pages, membership websites, newsletter portals, course platforms, and any website where users need to create an account.
This example validates the full name, email address, password strength, password confirmation, and terms checkbox. It uses inline error messages, success states, a password strength indicator, and a final success message when all fields are valid.
- Real-time JavaScript validation while users type
- Validates name, email, password, confirm password, and terms checkbox
- Includes inline error messages and success states
- Shows a password strength meter
- Good for signup forms, account forms, membership forms, and SaaS onboarding
Create a smoother signup flow with instant feedback
Validate important fields before submission and help users complete the form with fewer mistakes.
- Inline field messages
- Password strength indicator
- Success and error states
- Terms checkbox validation
Create your account
Try typing in the fields. The form validates each field and shows helpful messages before submission.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-signup-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-signup-validation-form]");
const result = demo.querySelector("[data-js-signup-result]");
const strengthBar = demo.querySelector("[data-js-signup-strength]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
password: form.querySelector("[name='password']"),
confirm: form.querySelector("[name='confirm']"),
terms: form.querySelector("[name='terms']")
};
function setFieldState(fieldName, isValid, message) {
const fieldWrap = demo.querySelector("[data-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-message='" + fieldName + "']");
if (!fieldWrap || !messageBox) {
return;
}
fieldWrap.classList.remove("is-valid", "is-error");
if (message) {
fieldWrap.classList.add(isValid ? "is-valid" : "is-error");
}
messageBox.textContent = message;
}
function setTermsState(isValid, message) {
const messageBox = demo.querySelector("[data-message='terms']");
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#ef4444";
}
function getPasswordStrength(password) {
let score = 0;
if (password.length >= 8) {
score++;
}
if (/[A-Z]/.test(password)) {
score++;
}
if (/[0-9]/.test(password)) {
score++;
}
if (/[^A-Za-z0-9]/.test(password)) {
score++;
}
return score;
}
function validateName() {
const value = fields.name.value.trim();
if (value.length < 2) {
setFieldState("name", false, "Please enter at least 2 characters.");
return false;
}
setFieldState("name", true, "Looks good.");
return true;
}
function validateEmail() {
const value = fields.email.value.trim();
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(value)) {
setFieldState("email", false, "Please enter a valid email address.");
return false;
}
setFieldState("email", true, "Email format is valid.");
return true;
}
function validatePassword() {
const value = fields.password.value;
const strength = getPasswordStrength(value);
strengthBar.setAttribute("data-strength", String(strength));
if (value.length < 8) {
setFieldState("password", false, "Password must be at least 8 characters.");
return false;
}
if (strength < 3) {
setFieldState("password", false, "Use uppercase letters, numbers, or symbols for a stronger password.");
return false;
}
setFieldState("password", true, "Strong password.");
return true;
}
function validateConfirm() {
const password = fields.password.value;
const confirm = fields.confirm.value;
if (!confirm) {
setFieldState("confirm", false, "Please repeat your password.");
return false;
}
if (password !== confirm) {
setFieldState("confirm", false, "Passwords do not match.");
return false;
}
setFieldState("confirm", true, "Passwords match.");
return true;
}
function validateTerms() {
if (!fields.terms.checked) {
setTermsState(false, "Please accept the terms before submitting.");
return false;
}
setTermsState(true, "Terms accepted.");
return true;
}
function validateForm() {
const isNameValid = validateName();
const isEmailValid = validateEmail();
const isPasswordValid = validatePassword();
const isConfirmValid = validateConfirm();
const isTermsValid = validateTerms();
return isNameValid && isEmailValid && isPasswordValid && isConfirmValid && isTermsValid;
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.password.addEventListener("input", function () {
validatePassword();
validateConfirm();
result.classList.remove("is-visible");
});
fields.confirm.addEventListener("input", function () {
validateConfirm();
result.classList.remove("is-visible");
});
fields.terms.addEventListener("change", function () {
validateTerms();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateForm()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
});
})();
HTML
<div class="js-signup-validation-preview" data-js-signup-validation-demo>
<div class="js-signup-validation-panel">
<div>
<div class="js-signup-validation-kicker">Live Validation</div>
<h3>Create a smoother signup flow with instant feedback</h3>
<p>Validate important fields before submission and help users complete the form with fewer mistakes.</p>
</div>
<ul class="js-signup-validation-benefits">
<li>Inline field messages</li>
<li>Password strength indicator</li>
<li>Success and error states</li>
<li>Terms checkbox validation</li>
</ul>
</div>
<div class="js-signup-validation-card">
<h3>Create your account</h3>
<p class="js-signup-validation-card-text">Try typing in the fields. The form validates each field and shows helpful messages before submission.</p>
<form class="js-signup-validation-form" novalidate data-js-signup-validation-form>
<div class="js-signup-validation-field" data-field="name">
<label for="signup-demo-name">Full name</label>
<div class="js-signup-validation-input-wrap">
<input id="signup-demo-name" name="name" type="text" placeholder="Example: Sarah Johnson">
<span class="js-signup-validation-status" aria-hidden="true"></span>
</div>
<div class="js-signup-validation-message" data-message="name"></div>
</div>
<div class="js-signup-validation-field" data-field="email">
<label for="signup-demo-email">Email address</label>
<div class="js-signup-validation-input-wrap">
<input id="signup-demo-email" name="email" type="email" placeholder="you@example.com">
<span class="js-signup-validation-status" aria-hidden="true"></span>
</div>
<div class="js-signup-validation-message" data-message="email"></div>
</div>
<div class="js-signup-validation-field" data-field="password">
<label for="signup-demo-password">Password</label>
<div class="js-signup-validation-input-wrap">
<input id="signup-demo-password" name="password" type="password" placeholder="At least 8 characters">
<span class="js-signup-validation-status" aria-hidden="true"></span>
</div>
<div class="js-signup-validation-strength" data-js-signup-strength data-strength="0">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="js-signup-validation-message" data-message="password"></div>
</div>
<div class="js-signup-validation-field" data-field="confirm">
<label for="signup-demo-confirm">Confirm password</label>
<div class="js-signup-validation-input-wrap">
<input id="signup-demo-confirm" name="confirm" type="password" placeholder="Repeat your password">
<span class="js-signup-validation-status" aria-hidden="true"></span>
</div>
<div class="js-signup-validation-message" data-message="confirm"></div>
</div>
<div class="js-signup-validation-check">
<input id="signup-demo-terms" name="terms" type="checkbox">
<label for="signup-demo-terms">I agree to the terms and privacy policy before creating my account.</label>
<div class="js-signup-validation-check-message" data-message="terms"></div>
</div>
<button class="js-signup-validation-submit" type="submit">Create Account</button>
<div class="js-signup-validation-result" data-js-signup-result>
Success! This demo form is valid. Connect it to your real backend or signup system in production.
</div>
</form>
</div>
</div>
CSS
.js-signup-validation-preview {
position: relative;
display: grid;
grid-template-columns: minmax(0, 0.95fr) minmax(340px, 1.05fr);
gap: 26px;
align-items: stretch;
min-height: 640px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 12%, rgba(56, 189, 248, 0.18), transparent 32%),
radial-gradient(circle at 88% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
linear-gradient(135deg, #eff6ff 0%, #ffffff 48%, #faf5ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-signup-validation-panel {
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 28px;
padding: 34px;
border-radius: 30px;
background:
radial-gradient(circle at 24% 20%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #0f172a 0%, #1d4ed8 52%, #7c3aed 100%);
color: #ffffff;
box-shadow: 0 26px 80px rgba(30, 64, 175, 0.28);
}
.js-signup-validation-panel::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 260px;
height: 260px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-signup-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #dbeafe;
font-size: 13px;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-signup-validation-panel h3 {
position: relative;
z-index: 2;
max-width: 440px;
margin: 0;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 62px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.07em;
}
.js-signup-validation-panel p {
position: relative;
z-index: 2;
max-width: 440px;
margin: 0;
color: #dbeafe;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-signup-validation-benefits {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.js-signup-validation-benefits li {
display: flex;
align-items: center;
gap: 10px;
margin: 0;
color: #f8fafc;
font-size: 14px;
font-weight: 800;
line-height: 1.45;
}
.js-signup-validation-benefits li::before {
content: "✓";
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 22px;
height: 22px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
color: #bfdbfe;
font-size: 13px;
font-weight: 950;
}
.js-signup-validation-card {
display: flex;
flex-direction: column;
justify-content: center;
padding: 34px;
border-radius: 30px;
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.14);
backdrop-filter: blur(14px);
}
.js-signup-validation-card h3 {
margin: 0 0 10px;
color: #0f172a;
font-size: 34px;
line-height: 1.1;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-signup-validation-card-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-signup-validation-form {
display: grid;
gap: 16px;
}
.js-signup-validation-field {
display: grid;
gap: 7px;
}
.js-signup-validation-field label {
color: #0f172a;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-signup-validation-input-wrap {
position: relative;
}
.js-signup-validation-field input {
width: 100%;
box-sizing: border-box;
min-height: 50px;
padding: 13px 44px 13px 14px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 16px;
background: #f8fafc;
color: #0f172a;
font-size: 15px;
font-weight: 650;
line-height: 1.4;
outline: none;
transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.js-signup-validation-field input:focus {
background: #ffffff;
border-color: rgba(37, 99, 235, 0.62);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
}
.js-signup-validation-status {
position: absolute;
top: 50%;
right: 14px;
transform: translateY(-50%);
display: none;
align-items: center;
justify-content: center;
width: 23px;
height: 23px;
border-radius: 999px;
font-size: 13px;
font-weight: 950;
}
.js-signup-validation-field.is-valid .js-signup-validation-status {
display: inline-flex;
background: #dcfce7;
color: #15803d;
}
.js-signup-validation-field.is-valid .js-signup-validation-status::before {
content: "✓";
}
.js-signup-validation-field.is-error .js-signup-validation-status {
display: inline-flex;
background: #fee2e2;
color: #b91c1c;
}
.js-signup-validation-field.is-error .js-signup-validation-status::before {
content: "!";
}
.js-signup-validation-field.is-valid input {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-signup-validation-field.is-error input {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-signup-validation-message {
min-height: 18px;
color: #ef4444;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-signup-validation-field.is-valid .js-signup-validation-message {
color: #15803d;
}
.js-signup-validation-strength {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
margin-top: 2px;
}
.js-signup-validation-strength span {
height: 6px;
border-radius: 999px;
background: #e2e8f0;
transition: background 0.2s ease;
}
.js-signup-validation-strength[data-strength="1"] span:nth-child(1) {
background: #ef4444;
}
.js-signup-validation-strength[data-strength="2"] span:nth-child(1),
.js-signup-validation-strength[data-strength="2"] span:nth-child(2) {
background: #f59e0b;
}
.js-signup-validation-strength[data-strength="3"] span:nth-child(1),
.js-signup-validation-strength[data-strength="3"] span:nth-child(2),
.js-signup-validation-strength[data-strength="3"] span:nth-child(3) {
background: #3b82f6;
}
.js-signup-validation-strength[data-strength="4"] span {
background: #22c55e;
}
.js-signup-validation-check {
display: grid;
grid-template-columns: 20px 1fr;
gap: 10px;
align-items: start;
padding: 14px;
border-radius: 16px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-signup-validation-check input {
width: 18px;
height: 18px;
margin-top: 2px;
accent-color: #2563eb;
}
.js-signup-validation-check label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 700;
}
.js-signup-validation-check-message {
grid-column: 1 / -1;
min-height: 16px;
color: #ef4444;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-signup-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 52px;
padding: 15px 20px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #2563eb, #7c3aed);
color: #ffffff;
font-size: 15px;
font-weight: 950;
line-height: 1;
cursor: pointer;
box-shadow: 0 18px 42px rgba(37, 99, 235, 0.28);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-signup-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(37, 99, 235, 0.36);
}
.js-signup-validation-result {
display: none;
padding: 14px 16px;
border-radius: 16px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 800;
}
.js-signup-validation-result.is-visible {
display: block;
}
@media (max-width: 900px) {
.js-signup-validation-preview {
grid-template-columns: 1fr;
}
.js-signup-validation-panel {
min-height: 360px;
}
}
@media (max-width: 640px) {
.js-signup-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-signup-validation-panel,
.js-signup-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-signup-validation-panel h3 {
font-size: 36px;
}
.js-signup-validation-card h3 {
font-size: 28px;
}
}
This real-time signup validation example is a strong base for account registration forms. You can reuse the same validation structure for user portals, SaaS products, membership websites, course platforms, community websites, and newsletter signup flows.
2. Checkout Form Validation
Checkout form validation helps ecommerce customers complete payment and shipping details without confusion. Instead of showing one generic error after submit, this example checks billing details, delivery fields, card number, expiry date, CVV, and terms acceptance with field-specific feedback.
The design is completely different from the first example. It uses a dark ecommerce checkout layout, a cart summary panel, grouped validation sections, compact payment fields, and a secure checkout message. This pattern is useful for product checkout pages, booking payments, subscription forms, order forms, and donation forms.
- Checkout-style JavaScript validation layout
- Validates billing name, email, address, card number, expiry date, CVV, and terms
- Uses grouped sections for customer, shipping, and payment details
- Includes order summary card and secure checkout styling
- Good for ecommerce stores, booking websites, subscription forms, and payment flows
Secure checkout
Validate customer, delivery, and payment details before the order is submitted.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-checkout-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-checkout-validation-form]");
const result = demo.querySelector("[data-js-checkout-result]");
const fields = {
fullName: form.querySelector("[name='fullName']"),
email: form.querySelector("[name='email']"),
address: form.querySelector("[name='address']"),
city: form.querySelector("[name='city']"),
zip: form.querySelector("[name='zip']"),
card: form.querySelector("[name='card']"),
expiry: form.querySelector("[name='expiry']"),
cvv: form.querySelector("[name='cvv']"),
terms: form.querySelector("[name='terms']")
};
function setCheckoutFieldState(fieldName, isValid, message) {
const fieldWrap = demo.querySelector("[data-checkout-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-checkout-message='" + fieldName + "']");
if (!fieldWrap || !messageBox) {
return;
}
fieldWrap.classList.remove("is-valid", "is-error");
fieldWrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function setTermsState(isValid, message) {
const messageBox = demo.querySelector("[data-checkout-message='terms']");
messageBox.textContent = message;
messageBox.style.color = isValid ? "#047857" : "#ef4444";
}
function validateRequired(fieldName, minLength, errorMessage, successMessage) {
const value = fields[fieldName].value.trim();
if (value.length < minLength) {
setCheckoutFieldState(fieldName, false, errorMessage);
return false;
}
setCheckoutFieldState(fieldName, true, successMessage);
return true;
}
function validateEmail() {
const value = fields.email.value.trim();
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(value)) {
setCheckoutFieldState("email", false, "Enter a valid email address.");
return false;
}
setCheckoutFieldState("email", true, "Email looks valid.");
return true;
}
function validateCard() {
const digits = fields.card.value.replace(/\D/g, "");
if (digits.length < 13 || digits.length > 19) {
setCheckoutFieldState("card", false, "Enter a valid card number.");
return false;
}
setCheckoutFieldState("card", true, "Card number format looks valid.");
return true;
}
function validateExpiry() {
const value = fields.expiry.value.trim();
const pattern = /^(0[1-9]|1[0-2])\/\d{2}$/;
if (!pattern.test(value)) {
setCheckoutFieldState("expiry", false, "Use MM/YY format.");
return false;
}
setCheckoutFieldState("expiry", true, "Expiry format is valid.");
return true;
}
function validateCvv() {
const value = fields.cvv.value.trim();
const pattern = /^\d{3,4}$/;
if (!pattern.test(value)) {
setCheckoutFieldState("cvv", false, "Use 3 or 4 digits.");
return false;
}
setCheckoutFieldState("cvv", true, "CVV format is valid.");
return true;
}
function validateZip() {
const value = fields.zip.value.trim();
if (value.length < 3) {
setCheckoutFieldState("zip", false, "Enter a valid ZIP or postal code.");
return false;
}
setCheckoutFieldState("zip", true, "Postal code looks valid.");
return true;
}
function validateTerms() {
if (!fields.terms.checked) {
setTermsState(false, "Please accept the terms before placing the order.");
return false;
}
setTermsState(true, "Terms accepted.");
return true;
}
function validateCheckout() {
const validName = validateRequired("fullName", 2, "Enter your full name.", "Name looks good.");
const validEmail = validateEmail();
const validAddress = validateRequired("address", 5, "Enter a complete street address.", "Address looks good.");
const validCity = validateRequired("city", 2, "Enter your city.", "City looks good.");
const validZip = validateZip();
const validCard = validateCard();
const validExpiry = validateExpiry();
const validCvv = validateCvv();
const validTerms = validateTerms();
return validName && validEmail && validAddress && validCity && validZip && validCard && validExpiry && validCvv && validTerms;
}
fields.card.addEventListener("input", function () {
const digits = fields.card.value.replace(/\D/g, "").slice(0, 19);
fields.card.value = digits.replace(/(.{4})/g, "$1 ").trim();
validateCard();
result.classList.remove("is-visible");
});
fields.expiry.addEventListener("input", function () {
let value = fields.expiry.value.replace(/\D/g, "").slice(0, 4);
if (value.length >= 3) {
value = value.slice(0, 2) + "/" + value.slice(2);
}
fields.expiry.value = value;
validateExpiry();
result.classList.remove("is-visible");
});
fields.cvv.addEventListener("input", function () {
fields.cvv.value = fields.cvv.value.replace(/\D/g, "").slice(0, 4);
validateCvv();
result.classList.remove("is-visible");
});
fields.fullName.addEventListener("input", function () {
validateRequired("fullName", 2, "Enter your full name.", "Name looks good.");
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.address.addEventListener("input", function () {
validateRequired("address", 5, "Enter a complete street address.", "Address looks good.");
result.classList.remove("is-visible");
});
fields.city.addEventListener("input", function () {
validateRequired("city", 2, "Enter your city.", "City looks good.");
result.classList.remove("is-visible");
});
fields.zip.addEventListener("input", function () {
validateZip();
result.classList.remove("is-visible");
});
fields.terms.addEventListener("change", function () {
validateTerms();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateCheckout()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
});
})();
HTML
<div class="js-checkout-validation-preview" data-js-checkout-validation-demo>
<div class="js-checkout-validation-main">
<div class="js-checkout-validation-header">
<div class="js-checkout-validation-header-content">
<h3>Secure checkout</h3>
<p>Validate customer, delivery, and payment details before the order is submitted.</p>
</div>
<div class="js-checkout-validation-lock" aria-hidden="true">🔒</div>
</div>
<form class="js-checkout-validation-form" novalidate data-js-checkout-validation-form>
<div class="js-checkout-validation-section">
<h3 class="js-checkout-validation-section-title"><span>1</span> Customer details</h3>
<div class="js-checkout-validation-grid">
<div class="js-checkout-validation-field" data-checkout-field="fullName">
<label for="checkout-demo-name">Full name</label>
<input id="checkout-demo-name" name="fullName" type="text" placeholder="Alex Morgan">
<div class="js-checkout-validation-message" data-checkout-message="fullName"></div>
</div>
<div class="js-checkout-validation-field" data-checkout-field="email">
<label for="checkout-demo-email">Email address</label>
<input id="checkout-demo-email" name="email" type="email" placeholder="alex@example.com">
<div class="js-checkout-validation-message" data-checkout-message="email"></div>
</div>
</div>
</div>
<div class="js-checkout-validation-section">
<h3 class="js-checkout-validation-section-title"><span>2</span> Delivery address</h3>
<div class="js-checkout-validation-grid">
<div class="js-checkout-validation-field is-full" data-checkout-field="address">
<label for="checkout-demo-address">Street address</label>
<input id="checkout-demo-address" name="address" type="text" placeholder="24 Market Street">
<div class="js-checkout-validation-message" data-checkout-message="address"></div>
</div>
<div class="js-checkout-validation-field" data-checkout-field="city">
<label for="checkout-demo-city">City</label>
<input id="checkout-demo-city" name="city" type="text" placeholder="New York">
<div class="js-checkout-validation-message" data-checkout-message="city"></div>
</div>
<div class="js-checkout-validation-field" data-checkout-field="zip">
<label for="checkout-demo-zip">ZIP / postal code</label>
<input id="checkout-demo-zip" name="zip" type="text" placeholder="10001">
<div class="js-checkout-validation-message" data-checkout-message="zip"></div>
</div>
</div>
</div>
<div class="js-checkout-validation-section">
<h3 class="js-checkout-validation-section-title"><span>3</span> Payment details</h3>
<div class="js-checkout-validation-card-row">
<div class="js-checkout-validation-field" data-checkout-field="card">
<label for="checkout-demo-card">Card number</label>
<input id="checkout-demo-card" name="card" type="text" inputmode="numeric" placeholder="4242 4242 4242 4242">
<div class="js-checkout-validation-message" data-checkout-message="card"></div>
</div>
<div class="js-checkout-validation-field" data-checkout-field="expiry">
<label for="checkout-demo-expiry">Expiry</label>
<input id="checkout-demo-expiry" name="expiry" type="text" placeholder="MM/YY">
<div class="js-checkout-validation-message" data-checkout-message="expiry"></div>
</div>
<div class="js-checkout-validation-field" data-checkout-field="cvv">
<label for="checkout-demo-cvv">CVV</label>
<input id="checkout-demo-cvv" name="cvv" type="text" inputmode="numeric" placeholder="123">
<div class="js-checkout-validation-message" data-checkout-message="cvv"></div>
</div>
</div>
</div>
<div class="js-checkout-validation-terms">
<input id="checkout-demo-terms" name="terms" type="checkbox">
<label for="checkout-demo-terms">I confirm that my order details are correct and I accept the terms of purchase.</label>
<div class="js-checkout-validation-terms-message" data-checkout-message="terms"></div>
</div>
<button class="js-checkout-validation-submit" type="submit">Place Secure Order</button>
<div class="js-checkout-validation-result" data-js-checkout-result>
Checkout validation passed. This is a front-end demo, so connect it to your real payment and backend validation before production use.
</div>
</form>
</div>
<aside class="js-checkout-validation-summary" aria-label="Order summary">
<div class="js-checkout-validation-summary-top">
<div class="js-checkout-validation-badge">Order Summary</div>
<h3>Website UI Kit Bundle</h3>
</div>
<div class="js-checkout-validation-items">
<div class="js-checkout-validation-item">
<div class="js-checkout-validation-thumb"></div>
<div>
<strong>Form UI Kit</strong>
<span>Validation-ready inputs</span>
</div>
<div class="js-checkout-validation-price">$39</div>
</div>
<div class="js-checkout-validation-item">
<div class="js-checkout-validation-thumb"></div>
<div>
<strong>Checkout Components</strong>
<span>Payment form layouts</span>
</div>
<div class="js-checkout-validation-price">$49</div>
</div>
</div>
<div class="js-checkout-validation-total">
<div class="js-checkout-validation-total-row">
<span>Subtotal</span>
<strong>$88</strong>
</div>
<div class="js-checkout-validation-total-row">
<span>Discount</span>
<strong>-$18</strong>
</div>
<div class="js-checkout-validation-total-row is-total">
<span>Total</span>
<strong>$70</strong>
</div>
<div class="js-checkout-validation-safe">Demo checkout only. Always validate payment and order data on the server in real projects.</div>
</div>
</aside>
</div>
CSS
.js-checkout-validation-preview {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) 340px;
gap: 24px;
align-items: start;
min-height: 680px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 10% 10%, rgba(16, 185, 129, 0.18), transparent 34%),
radial-gradient(circle at 92% 12%, rgba(245, 158, 11, 0.15), transparent 34%),
linear-gradient(135deg, #020617 0%, #0f172a 52%, #111827 100%);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.js-checkout-validation-main {
overflow: hidden;
border-radius: 30px;
background: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.10);
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.30);
}
.js-checkout-validation-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
padding: 26px;
background:
radial-gradient(circle at 20% 20%, rgba(255, 255, 255, 0.16), transparent 30%),
linear-gradient(135deg, #047857, #0f766e 52%, #0f172a);
}
.js-checkout-validation-header-content h3 {
margin: 0 0 8px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 34px;
line-height: 1.1;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-checkout-validation-header-content p {
margin: 0;
color: #d1fae5;
font-size: 15px;
line-height: 1.6;
font-weight: 650;
}
.js-checkout-validation-lock {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 56px;
height: 56px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.14);
color: #ffffff;
font-size: 26px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
.js-checkout-validation-form {
display: grid;
gap: 22px;
padding: 26px;
}
.js-checkout-validation-section {
display: grid;
gap: 16px;
}
.js-checkout-validation-section-title {
display: flex;
align-items: center;
gap: 10px;
margin: 0;
color: #0f172a;
font-size: 18px;
line-height: 1.25;
font-weight: 950;
letter-spacing: -0.025em;
}
.js-checkout-validation-section-title span {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 999px;
background: #ecfdf5;
color: #047857;
font-size: 13px;
font-weight: 950;
}
.js-checkout-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-checkout-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-checkout-validation-field {
display: grid;
gap: 7px;
}
.js-checkout-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-checkout-validation-field input {
width: 100%;
box-sizing: border-box;
min-height: 50px;
padding: 13px 14px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 15px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-checkout-validation-field input:focus {
background: #ffffff;
border-color: rgba(16, 185, 129, 0.65);
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.12);
}
.js-checkout-validation-field.is-error input {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-checkout-validation-field.is-valid input {
border-color: rgba(16, 185, 129, 0.58);
background: #f0fdf4;
}
.js-checkout-validation-message {
min-height: 17px;
color: #ef4444;
font-size: 12.5px;
line-height: 1.35;
font-weight: 750;
}
.js-checkout-validation-field.is-valid .js-checkout-validation-message {
color: #047857;
}
.js-checkout-validation-card-row {
display: grid;
grid-template-columns: 1fr 110px 90px;
gap: 14px;
}
.js-checkout-validation-terms {
display: grid;
grid-template-columns: 20px 1fr;
gap: 10px;
padding: 14px;
border-radius: 16px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-checkout-validation-terms input {
width: 18px;
height: 18px;
margin-top: 2px;
accent-color: #047857;
}
.js-checkout-validation-terms label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 700;
}
.js-checkout-validation-terms-message {
grid-column: 1 / -1;
min-height: 16px;
color: #ef4444;
font-size: 13px;
font-weight: 750;
}
.js-checkout-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #059669, #0f766e);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 44px rgba(5, 150, 105, 0.28);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-checkout-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(5, 150, 105, 0.36);
}
.js-checkout-validation-result {
display: none;
padding: 14px 16px;
border-radius: 16px;
background: #ecfdf5;
border: 1px solid rgba(16, 185, 129, 0.22);
color: #065f46;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-checkout-validation-result.is-visible {
display: block;
}
.js-checkout-validation-summary {
position: sticky;
top: 20px;
overflow: hidden;
border-radius: 30px;
background: rgba(255, 255, 255, 0.10);
border: 1px solid rgba(255, 255, 255, 0.13);
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.28);
backdrop-filter: blur(16px);
}
.js-checkout-validation-summary-top {
padding: 24px;
border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}
.js-checkout-validation-badge {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 14px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(16, 185, 129, 0.14);
border: 1px solid rgba(16, 185, 129, 0.24);
color: #a7f3d0;
font-size: 12px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.js-checkout-validation-summary h3 {
margin: 0;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 28px;
line-height: 1.12;
font-weight: 950;
letter-spacing: -0.045em;
}
.js-checkout-validation-items {
display: grid;
gap: 14px;
padding: 20px 24px;
}
.js-checkout-validation-item {
display: grid;
grid-template-columns: 54px 1fr auto;
gap: 12px;
align-items: center;
}
.js-checkout-validation-thumb {
width: 54px;
height: 54px;
border-radius: 18px;
background:
radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.26), transparent 30%),
linear-gradient(135deg, #10b981, #2563eb);
}
.js-checkout-validation-item strong {
display: block;
margin-bottom: 4px;
color: #ffffff;
font-size: 14px;
line-height: 1.2;
font-weight: 900;
}
.js-checkout-validation-item span {
display: block;
color: #cbd5e1;
font-size: 12px;
line-height: 1.3;
font-weight: 650;
}
.js-checkout-validation-price {
color: #ffffff;
font-size: 14px;
font-weight: 950;
}
.js-checkout-validation-total {
display: grid;
gap: 12px;
padding: 20px 24px 24px;
border-top: 1px solid rgba(255, 255, 255, 0.10);
}
.js-checkout-validation-total-row {
display: flex;
justify-content: space-between;
gap: 14px;
color: #cbd5e1;
font-size: 14px;
font-weight: 750;
}
.js-checkout-validation-total-row.is-total {
color: #ffffff;
font-size: 22px;
font-weight: 950;
}
.js-checkout-validation-safe {
margin-top: 6px;
padding: 13px;
border-radius: 16px;
background: rgba(16, 185, 129, 0.12);
color: #a7f3d0;
font-size: 13px;
line-height: 1.45;
font-weight: 750;
}
@media (max-width: 980px) {
.js-checkout-validation-preview {
grid-template-columns: 1fr;
}
.js-checkout-validation-summary {
position: relative;
top: auto;
}
}
@media (max-width: 700px) {
.js-checkout-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-checkout-validation-header {
align-items: flex-start;
}
.js-checkout-validation-grid,
.js-checkout-validation-card-row {
grid-template-columns: 1fr;
}
.js-checkout-validation-header-content h3 {
font-size: 28px;
}
}
This checkout form validation example is useful for ecommerce payment forms, service booking forms, paid membership forms, subscription checkout pages, and order forms. In production, JavaScript validation should improve the customer experience, while server-side validation should still protect payment, order, and customer data.
3. Contact Form Validation with Error Summary
Contact form validation with an error summary is useful when a form has several required fields and you want users to understand all problems at once. Instead of only showing inline messages, this pattern also displays a clear error summary at the top of the form after submit.
This example validates name, email, topic, message length, and consent checkbox. The layout uses a clean split contact section with a support information card on the left and a modern validation form on the right.
- Contact form validation with top error summary
- Validates name, email, topic, message length, and consent checkbox
- Shows inline field errors and a global error list
- Includes a success message after valid submit
- Good for service websites, agency contact pages, support forms, and quote request forms
Help visitors send better messages
Use field-specific validation and an error summary so users can quickly understand what needs to be fixed before sending a request.
Send a message
Submit the empty form to see the error summary, then fill in the fields to watch each validation state update.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-contact-summary-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-contact-summary-form]");
const alertBox = demo.querySelector("[data-js-contact-alert]");
const alertList = demo.querySelector("[data-js-contact-alert-list]");
const successBox = demo.querySelector("[data-js-contact-success]");
const countBox = demo.querySelector("[data-js-contact-count]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
topic: form.querySelector("[name='topic']"),
message: form.querySelector("[name='message']"),
consent: form.querySelector("[name='consent']")
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-contact-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-contact-message='" + fieldName + "']");
if (!wrap || !messageBox) {
return;
}
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function setConsent(isValid, message) {
const messageBox = demo.querySelector("[data-contact-message='consent']");
messageBox.textContent = message;
messageBox.style.color = isValid ? "#047857" : "#e11d48";
}
function validateName() {
const value = fields.name.value.trim();
if (value.length < 2) {
setField("name", false, "Enter your name.");
return "Name is required.";
}
setField("name", true, "Name looks good.");
return "";
}
function validateEmail() {
const value = fields.email.value.trim();
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(value)) {
setField("email", false, "Enter a valid email address.");
return "A valid email address is required.";
}
setField("email", true, "Email looks valid.");
return "";
}
function validateTopic() {
if (!fields.topic.value) {
setField("topic", false, "Choose a topic.");
return "Please choose a topic.";
}
setField("topic", true, "Topic selected.");
return "";
}
function validateMessage() {
const value = fields.message.value.trim();
countBox.textContent = String(value.length);
if (value.length < 40) {
setField("message", false, "Write at least 40 characters.");
return "Message must be at least 40 characters.";
}
setField("message", true, "Message length looks good.");
return "";
}
function validateConsent() {
if (!fields.consent.checked) {
setConsent(false, "Please accept before sending.");
return "Consent checkbox must be accepted.";
}
setConsent(true, "Consent accepted.");
return "";
}
function validateAll() {
const errors = [
validateName(),
validateEmail(),
validateTopic(),
validateMessage(),
validateConsent()
].filter(Boolean);
return errors;
}
function renderErrorSummary(errors) {
alertList.innerHTML = "";
errors.forEach(function (error) {
const item = document.createElement("li");
item.textContent = error;
alertList.appendChild(item);
});
alertBox.classList.toggle("is-visible", errors.length > 0);
successBox.classList.toggle("is-visible", errors.length === 0);
}
fields.name.addEventListener("input", function () {
validateName();
alertBox.classList.remove("is-visible");
successBox.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
alertBox.classList.remove("is-visible");
successBox.classList.remove("is-visible");
});
fields.topic.addEventListener("change", function () {
validateTopic();
alertBox.classList.remove("is-visible");
successBox.classList.remove("is-visible");
});
fields.message.addEventListener("input", function () {
validateMessage();
alertBox.classList.remove("is-visible");
successBox.classList.remove("is-visible");
});
fields.consent.addEventListener("change", function () {
validateConsent();
alertBox.classList.remove("is-visible");
successBox.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
renderErrorSummary(validateAll());
});
});
})();
HTML
<div class="js-contact-summary-preview" data-js-contact-summary-demo>
<div class="js-contact-summary-info">
<div>
<div class="js-contact-summary-kicker">Contact Validation</div>
<h3>Help visitors send better messages</h3>
<p>Use field-specific validation and an error summary so users can quickly understand what needs to be fixed before sending a request.</p>
</div>
<div class="js-contact-summary-channels">
<div class="js-contact-summary-channel">
<div class="js-contact-summary-icon">✉</div>
<div>
<strong>Email support</strong>
<span>Validate contact details before the message is sent.</span>
</div>
</div>
<div class="js-contact-summary-channel">
<div class="js-contact-summary-icon">☎</div>
<div>
<strong>Quote requests</strong>
<span>Collect cleaner project requests with fewer missing fields.</span>
</div>
</div>
</div>
</div>
<div class="js-contact-summary-card">
<h3>Send a message</h3>
<p class="js-contact-summary-lead">Submit the empty form to see the error summary, then fill in the fields to watch each validation state update.</p>
<div class="js-contact-summary-alert" data-js-contact-alert>
<strong>Please fix the following fields:</strong>
<ul data-js-contact-alert-list></ul>
</div>
<div class="js-contact-summary-success" data-js-contact-success>
Your message passed the front-end validation. Connect this demo to your real form handler in production.
</div>
<form class="js-contact-summary-form" novalidate data-js-contact-summary-form>
<div class="js-contact-summary-grid">
<div class="js-contact-summary-field" data-contact-field="name">
<label for="contact-summary-name">Name</label>
<input id="contact-summary-name" name="name" type="text" placeholder="Your name">
<div class="js-contact-summary-message" data-contact-message="name"></div>
</div>
<div class="js-contact-summary-field" data-contact-field="email">
<label for="contact-summary-email">Email</label>
<input id="contact-summary-email" name="email" type="email" placeholder="you@example.com">
<div class="js-contact-summary-message" data-contact-message="email"></div>
</div>
<div class="js-contact-summary-field is-full" data-contact-field="topic">
<label for="contact-summary-topic">Topic</label>
<select id="contact-summary-topic" name="topic">
<option value="">Choose a topic</option>
<option value="Website project">Website project</option>
<option value="Support request">Support request</option>
<option value="Quote request">Quote request</option>
<option value="Partnership">Partnership</option>
</select>
<div class="js-contact-summary-message" data-contact-message="topic"></div>
</div>
<div class="js-contact-summary-field is-full" data-contact-field="message">
<label for="contact-summary-message">Message</label>
<textarea id="contact-summary-message" name="message" placeholder="Tell us what you need..."></textarea>
<div class="js-contact-summary-counter"><span data-js-contact-count>0</span>/40 minimum characters</div>
<div class="js-contact-summary-message" data-contact-message="message"></div>
</div>
</div>
<div class="js-contact-summary-consent">
<input id="contact-summary-consent" name="consent" type="checkbox">
<label for="contact-summary-consent">I agree that this demo may validate my entered form fields before submission.</label>
<div class="js-contact-summary-consent-message" data-contact-message="consent"></div>
</div>
<button class="js-contact-summary-submit" type="submit">Send Message</button>
</form>
</div>
</div>
CSS
.js-contact-summary-preview {
display: grid;
grid-template-columns: minmax(280px, 0.88fr) minmax(0, 1.12fr);
gap: 26px;
align-items: stretch;
min-height: 660px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 18%, rgba(244, 63, 94, 0.13), transparent 34%),
radial-gradient(circle at 92% 12%, rgba(251, 146, 60, 0.15), transparent 34%),
linear-gradient(135deg, #fff7ed 0%, #ffffff 52%, #fff1f2 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-contact-summary-info {
position: relative;
overflow: hidden;
padding: 34px;
border-radius: 30px;
background:
radial-gradient(circle at 24% 20%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #7f1d1d 0%, #be123c 48%, #ea580c 100%);
color: #ffffff;
box-shadow: 0 26px 80px rgba(190, 18, 60, 0.24);
}
.js-contact-summary-info::before {
content: "";
position: absolute;
left: -90px;
bottom: -110px;
width: 260px;
height: 260px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-contact-summary-kicker {
position: relative;
z-index: 2;
display: inline-flex;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #ffe4e6;
font-size: 13px;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-contact-summary-info h3 {
position: relative;
z-index: 2;
max-width: 420px;
margin: 0 0 16px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.03;
font-weight: 950;
letter-spacing: -0.065em;
}
.js-contact-summary-info p {
position: relative;
z-index: 2;
max-width: 430px;
margin: 0 0 28px;
color: #ffe4e6;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-contact-summary-channels {
position: relative;
z-index: 2;
display: grid;
gap: 14px;
margin-top: 32px;
}
.js-contact-summary-channel {
display: grid;
grid-template-columns: 46px 1fr;
gap: 12px;
align-items: center;
padding: 14px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-contact-summary-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 46px;
height: 46px;
border-radius: 16px;
background: rgba(255, 255, 255, 0.16);
color: #ffffff;
font-size: 20px;
}
.js-contact-summary-channel strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 14px;
font-weight: 950;
}
.js-contact-summary-channel span {
display: block;
color: #ffe4e6;
font-size: 13px;
line-height: 1.4;
font-weight: 650;
}
.js-contact-summary-card {
padding: 34px;
border-radius: 30px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-contact-summary-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 34px;
line-height: 1.1;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-contact-summary-lead {
margin: 0 0 22px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-contact-summary-alert {
display: none;
margin-bottom: 18px;
padding: 16px;
border-radius: 18px;
background: #fff1f2;
border: 1px solid rgba(244, 63, 94, 0.22);
}
.js-contact-summary-alert.is-visible {
display: block;
}
.js-contact-summary-alert strong {
display: block;
margin-bottom: 8px;
color: #be123c;
font-size: 14px;
font-weight: 950;
}
.js-contact-summary-alert ul {
display: grid;
gap: 5px;
margin: 0;
padding-left: 18px;
color: #9f1239;
font-size: 13px;
line-height: 1.45;
font-weight: 750;
}
.js-contact-summary-success {
display: none;
margin-bottom: 18px;
padding: 15px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(16, 185, 129, 0.22);
color: #047857;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-contact-summary-success.is-visible {
display: block;
}
.js-contact-summary-form {
display: grid;
gap: 16px;
}
.js-contact-summary-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-contact-summary-grid .is-full {
grid-column: 1 / -1;
}
.js-contact-summary-field {
display: grid;
gap: 7px;
}
.js-contact-summary-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-contact-summary-field input,
.js-contact-summary-field select,
.js-contact-summary-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 50px;
padding: 13px 14px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 16px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-contact-summary-field textarea {
min-height: 130px;
resize: vertical;
}
.js-contact-summary-field input:focus,
.js-contact-summary-field select:focus,
.js-contact-summary-field textarea:focus {
background: #ffffff;
border-color: rgba(244, 63, 94, 0.62);
box-shadow: 0 0 0 4px rgba(244, 63, 94, 0.12);
}
.js-contact-summary-field.is-error input,
.js-contact-summary-field.is-error select,
.js-contact-summary-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-contact-summary-field.is-valid input,
.js-contact-summary-field.is-valid select,
.js-contact-summary-field.is-valid textarea {
border-color: rgba(16, 185, 129, 0.58);
background: #f0fdf4;
}
.js-contact-summary-message {
min-height: 17px;
color: #e11d48;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-contact-summary-field.is-valid .js-contact-summary-message {
color: #047857;
}
.js-contact-summary-counter {
display: flex;
justify-content: flex-end;
margin-top: -3px;
color: #94a3b8;
font-size: 12px;
font-weight: 800;
}
.js-contact-summary-consent {
display: grid;
grid-template-columns: 20px 1fr;
gap: 10px;
padding: 14px;
border-radius: 16px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-contact-summary-consent input {
width: 18px;
height: 18px;
margin-top: 2px;
accent-color: #e11d48;
}
.js-contact-summary-consent label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 700;
}
.js-contact-summary-consent-message {
grid-column: 1 / -1;
min-height: 16px;
color: #e11d48;
font-size: 13px;
font-weight: 750;
}
.js-contact-summary-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 52px;
padding: 15px 20px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #e11d48, #f97316);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(225, 29, 72, 0.26);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-contact-summary-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(225, 29, 72, 0.34);
}
@media (max-width: 920px) {
.js-contact-summary-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.js-contact-summary-preview {
padding: 18px;
border-radius: 24px;
}
.js-contact-summary-info,
.js-contact-summary-card {
padding: 24px;
border-radius: 24px;
}
.js-contact-summary-grid {
grid-template-columns: 1fr;
}
.js-contact-summary-grid .is-full {
grid-column: auto;
}
.js-contact-summary-info h3,
.js-contact-summary-card h3 {
font-size: 30px;
}
}
This contact form validation pattern is helpful when you want to combine inline field feedback with a clear error summary. It is especially useful for quote forms, support forms, project inquiry forms, and longer contact forms where users may miss one or more required fields.
4. Password Strength Checklist Validation
Password strength checklist validation helps users understand password requirements before they submit a signup or account form. Instead of showing only one generic password error, this pattern updates each rule in real time as the user types.
This example uses a bold account security layout with a password field, show/hide password button, live requirement checklist, animated strength score, and confirm password validation. It is useful for account registration, reset password pages, member areas, dashboard apps, and SaaS onboarding flows.
- Live password strength checklist validation
- Checks length, uppercase letter, number, special character, and password match
- Includes show/hide password button
- Uses a visual strength score and success message
- Good for signup, reset password, account security, and SaaS onboarding forms
Create a stronger password before signup
This validation pattern gives users live feedback for each password rule and confirms when both password fields match.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-password-checklist-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-password-checklist-form]");
const passwordInput = form.querySelector("[name='password']");
const confirmInput = form.querySelector("[name='confirm']");
const toggleButton = demo.querySelector("[data-js-password-toggle]");
const passwordMessage = demo.querySelector("[data-js-password-message]");
const confirmMessage = demo.querySelector("[data-js-confirm-message]");
const scoreBox = demo.querySelector("[data-js-password-score]");
const meter = demo.querySelector("[data-js-password-meter]");
const result = demo.querySelector("[data-js-password-result]");
const rules = {
length: demo.querySelector("[data-rule='length']"),
uppercase: demo.querySelector("[data-rule='uppercase']"),
number: demo.querySelector("[data-rule='number']"),
special: demo.querySelector("[data-rule='special']"),
match: demo.querySelector("[data-rule='match']")
};
function setRule(ruleName, isValid) {
rules[ruleName].classList.toggle("is-valid", isValid);
}
function getValidationState() {
const password = passwordInput.value;
const confirm = confirmInput.value;
return {
length: password.length >= 8,
uppercase: /[A-Z]/.test(password),
number: /[0-9]/.test(password),
special: /[^A-Za-z0-9]/.test(password),
match: password.length > 0 && password === confirm
};
}
function updateValidation() {
const state = getValidationState();
const score = Object.values(state).filter(Boolean).length;
setRule("length", state.length);
setRule("uppercase", state.uppercase);
setRule("number", state.number);
setRule("special", state.special);
setRule("match", state.match);
scoreBox.textContent = score + "/5";
meter.setAttribute("data-score", String(score));
if (!passwordInput.value) {
passwordMessage.textContent = "Start typing to check password strength.";
passwordMessage.classList.remove("is-valid");
} else if (score < 4) {
passwordMessage.textContent = "Password is not strong enough yet.";
passwordMessage.classList.remove("is-valid");
} else if (score === 4) {
passwordMessage.textContent = "Almost there. Confirm the same password.";
passwordMessage.classList.remove("is-valid");
} else {
passwordMessage.textContent = "Strong password.";
passwordMessage.classList.add("is-valid");
}
if (!confirmInput.value) {
confirmMessage.textContent = "Repeat the password to confirm it.";
confirmMessage.classList.remove("is-valid");
} else if (!state.match) {
confirmMessage.textContent = "Passwords do not match.";
confirmMessage.classList.remove("is-valid");
} else {
confirmMessage.textContent = "Passwords match.";
confirmMessage.classList.add("is-valid");
}
result.classList.remove("is-visible");
return score === 5;
}
toggleButton.addEventListener("click", function () {
const isPassword = passwordInput.type === "password";
passwordInput.type = isPassword ? "text" : "password";
confirmInput.type = isPassword ? "text" : "password";
toggleButton.textContent = isPassword ? "Hide" : "Show";
});
passwordInput.addEventListener("input", updateValidation);
confirmInput.addEventListener("input", updateValidation);
form.addEventListener("submit", function (event) {
event.preventDefault();
if (updateValidation()) {
result.classList.add("is-visible");
}
});
});
})();
HTML
<div class="js-password-checklist-preview" data-js-password-checklist-demo>
<div class="js-password-checklist-shell">
<div class="js-password-checklist-card">
<div class="js-password-checklist-badge">Security Validation</div>
<h3>Create a stronger password before signup</h3>
<p>This validation pattern gives users live feedback for each password rule and confirms when both password fields match.</p>
<form class="js-password-checklist-form" novalidate data-js-password-checklist-form>
<div class="js-password-checklist-field">
<label for="password-checklist-password">Password</label>
<div class="js-password-checklist-input-wrap">
<input id="password-checklist-password" name="password" type="password" placeholder="Create a strong password">
<button class="js-password-checklist-toggle" type="button" data-js-password-toggle>Show</button>
</div>
<div class="js-password-checklist-message" data-js-password-message>Start typing to check password strength.</div>
</div>
<div class="js-password-checklist-meter">
<div class="js-password-checklist-meter-head">
<span>Password strength</span>
<strong data-js-password-score>0/5</strong>
</div>
<div class="js-password-checklist-meter-track">
<div class="js-password-checklist-meter-fill" data-js-password-meter data-score="0"></div>
</div>
</div>
<div class="js-password-checklist-field">
<label for="password-checklist-confirm">Confirm password</label>
<div class="js-password-checklist-input-wrap">
<input id="password-checklist-confirm" name="confirm" type="password" placeholder="Repeat your password">
</div>
<div class="js-password-checklist-message" data-js-confirm-message></div>
</div>
<button class="js-password-checklist-submit" type="submit">Save Password</button>
<div class="js-password-checklist-result" data-js-password-result>
Password validation passed. This demo form is ready for front-end user feedback.
</div>
</form>
</div>
<aside class="js-password-checklist-rules">
<h3>Password rules</h3>
<p>Each rule updates automatically while the user types.</p>
<ul class="js-password-checklist-list">
<li data-rule="length">At least 8 characters</li>
<li data-rule="uppercase">One uppercase letter</li>
<li data-rule="number">One number</li>
<li data-rule="special">One special character</li>
<li data-rule="match">Passwords match</li>
</ul>
</aside>
</div>
</div>
CSS
.js-password-checklist-preview {
position: relative;
display: grid;
place-items: center;
min-height: 700px;
padding: 34px;
border-radius: 28px;
overflow: hidden;
background:
radial-gradient(circle at 18% 18%, rgba(34, 211, 238, 0.18), transparent 34%),
radial-gradient(circle at 82% 16%, rgba(129, 140, 248, 0.18), transparent 34%),
radial-gradient(circle at 50% 92%, rgba(16, 185, 129, 0.14), transparent 36%),
linear-gradient(135deg, #020617 0%, #111827 52%, #172554 100%);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.js-password-checklist-shell {
width: min(100%, 980px);
display: grid;
grid-template-columns: minmax(0, 1fr) 330px;
gap: 24px;
align-items: stretch;
}
.js-password-checklist-card,
.js-password-checklist-rules {
border-radius: 32px;
background: rgba(255, 255, 255, 0.095);
border: 1px solid rgba(255, 255, 255, 0.13);
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.28);
backdrop-filter: blur(18px);
}
.js-password-checklist-card {
padding: 34px;
}
.js-password-checklist-badge {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(34, 211, 238, 0.12);
border: 1px solid rgba(34, 211, 238, 0.24);
color: #a5f3fc;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-password-checklist-card h3 {
max-width: 560px;
margin: 0 0 12px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 60px);
line-height: 1.03;
font-weight: 950;
letter-spacing: -0.07em;
}
.js-password-checklist-card p {
max-width: 560px;
margin: 0 0 28px;
color: #cbd5e1;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-password-checklist-form {
display: grid;
gap: 18px;
}
.js-password-checklist-field {
display: grid;
gap: 8px;
}
.js-password-checklist-field label {
color: #f8fafc;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.03em;
}
.js-password-checklist-input-wrap {
position: relative;
}
.js-password-checklist-field input {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 92px 14px 15px;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 18px;
background: rgba(15, 23, 42, 0.70);
color: #ffffff;
font-size: 15px;
line-height: 1.4;
font-weight: 700;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-password-checklist-field input::placeholder {
color: rgba(203, 213, 225, 0.70);
}
.js-password-checklist-field input:focus {
border-color: rgba(34, 211, 238, 0.62);
background: rgba(15, 23, 42, 0.92);
box-shadow: 0 0 0 4px rgba(34, 211, 238, 0.12);
}
.js-password-checklist-toggle {
position: absolute;
top: 50%;
right: 9px;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 38px;
padding: 10px 12px;
border: 0;
border-radius: 999px;
background: rgba(255, 255, 255, 0.11);
color: #e0f2fe;
font-size: 12px;
line-height: 1;
font-weight: 900;
cursor: pointer;
}
.js-password-checklist-message {
min-height: 18px;
color: #fca5a5;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-password-checklist-message.is-valid {
color: #86efac;
}
.js-password-checklist-meter {
display: grid;
gap: 10px;
margin: 2px 0 4px;
}
.js-password-checklist-meter-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
color: #cbd5e1;
font-size: 13px;
font-weight: 850;
}
.js-password-checklist-meter-track {
height: 10px;
overflow: hidden;
border-radius: 999px;
background: rgba(255, 255, 255, 0.10);
}
.js-password-checklist-meter-fill {
width: 0%;
height: 100%;
border-radius: 999px;
background: linear-gradient(135deg, #ef4444, #f59e0b);
transition: width 0.24s ease, background 0.24s ease;
}
.js-password-checklist-meter-fill[data-score="1"] {
width: 20%;
background: linear-gradient(135deg, #ef4444, #f97316);
}
.js-password-checklist-meter-fill[data-score="2"] {
width: 40%;
background: linear-gradient(135deg, #f97316, #f59e0b);
}
.js-password-checklist-meter-fill[data-score="3"] {
width: 60%;
background: linear-gradient(135deg, #f59e0b, #3b82f6);
}
.js-password-checklist-meter-fill[data-score="4"] {
width: 80%;
background: linear-gradient(135deg, #3b82f6, #22c55e);
}
.js-password-checklist-meter-fill[data-score="5"] {
width: 100%;
background: linear-gradient(135deg, #22c55e, #14b8a6);
}
.js-password-checklist-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #06b6d4, #2563eb, #7c3aed);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 20px 48px rgba(37, 99, 235, 0.32);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-password-checklist-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 24px 58px rgba(37, 99, 235, 0.42);
}
.js-password-checklist-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: rgba(16, 185, 129, 0.13);
border: 1px solid rgba(16, 185, 129, 0.24);
color: #a7f3d0;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-password-checklist-result.is-visible {
display: block;
}
.js-password-checklist-rules {
padding: 26px;
}
.js-password-checklist-rules h3 {
margin: 0 0 10px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 24px;
line-height: 1.12;
font-weight: 950;
letter-spacing: -0.04em;
}
.js-password-checklist-rules p {
margin: 0 0 20px;
color: #cbd5e1;
font-size: 14px;
line-height: 1.65;
font-weight: 650;
}
.js-password-checklist-list {
display: grid;
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.js-password-checklist-list li {
display: grid;
grid-template-columns: 26px 1fr;
gap: 10px;
align-items: center;
margin: 0;
padding: 13px;
border-radius: 16px;
background: rgba(15, 23, 42, 0.56);
border: 1px solid rgba(255, 255, 255, 0.10);
color: #cbd5e1;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.js-password-checklist-list li::before {
content: "×";
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border-radius: 999px;
background: rgba(239, 68, 68, 0.16);
color: #fca5a5;
font-size: 14px;
font-weight: 950;
}
.js-password-checklist-list li.is-valid {
background: rgba(16, 185, 129, 0.13);
border-color: rgba(16, 185, 129, 0.28);
color: #d1fae5;
}
.js-password-checklist-list li.is-valid::before {
content: "✓";
background: rgba(16, 185, 129, 0.18);
color: #86efac;
}
@media (max-width: 920px) {
.js-password-checklist-shell {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.js-password-checklist-preview {
padding: 18px;
border-radius: 24px;
}
.js-password-checklist-card,
.js-password-checklist-rules {
padding: 24px;
border-radius: 24px;
}
.js-password-checklist-card h3 {
font-size: 34px;
}
}
This password checklist validation example makes password rules visible and easier to understand. It works especially well for registration forms, password reset screens, account security pages, SaaS onboarding flows, and member dashboard settings.
5. Email Validation Form with Typo Suggestions
Email validation with typo suggestions helps users fix common email mistakes before submitting a form. This pattern is useful for newsletter forms, account signup forms, lead capture forms, checkout forms, download forms, and any website where email accuracy matters.
This example checks whether the email field has a valid format and also suggests fixes for common domain typos such as gmail.con, gnail.com, hotmial.com, and yaho.com. The layout uses a clean email capture card with a side preview panel, suggestion button, and success state.
- Email format validation with JavaScript
- Detects common email domain typos
- Includes a clickable correction suggestion
- Shows inline error and success messages
- Good for newsletter forms, signup forms, lead forms, and checkout forms
Catch email mistakes before submit
Try examples like john@gmail.con, anna@gnail.com, or a normal email address to see format validation and typo suggestions.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-email-suggestion-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-email-suggestion-form]");
const field = demo.querySelector("[data-js-email-field]");
const input = form.querySelector("[name='email']");
const message = demo.querySelector("[data-js-email-message]");
const fixButton = demo.querySelector("[data-js-email-fix]");
const result = demo.querySelector("[data-js-email-result]");
const typoMap = {
"gmail.con": "gmail.com",
"gmai.com": "gmail.com",
"gmial.com": "gmail.com",
"gnail.com": "gmail.com",
"hotmial.com": "hotmail.com",
"hotmail.con": "hotmail.com",
"outlok.com": "outlook.com",
"outlook.con": "outlook.com",
"yaho.com": "yahoo.com",
"yahoo.con": "yahoo.com"
};
function resetState() {
field.classList.remove("is-valid", "is-error", "is-warning");
message.textContent = "";
fixButton.textContent = "";
fixButton.classList.remove("is-visible");
result.classList.remove("is-visible");
}
function getSuggestedEmail(email) {
const parts = email.split("@");
if (parts.length !== 2) {
return "";
}
const localPart = parts[0];
const domain = parts[1].toLowerCase();
if (!typoMap[domain]) {
return "";
}
return localPart + "@" + typoMap[domain];
}
function validateEmail(showSuccess) {
const value = input.value.trim();
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
resetState();
if (!value) {
field.classList.add("is-error");
message.textContent = "Please enter your email address.";
return false;
}
if (!emailPattern.test(value)) {
field.classList.add("is-error");
message.textContent = "Please enter a valid email address format.";
return false;
}
const suggestion = getSuggestedEmail(value);
if (suggestion) {
field.classList.add("is-warning");
message.textContent = "Did you mean " + suggestion + "?";
fixButton.textContent = "Use " + suggestion;
fixButton.classList.add("is-visible");
fixButton.dataset.suggestion = suggestion;
return false;
}
field.classList.add("is-valid");
message.textContent = "Email format looks good.";
if (showSuccess) {
result.classList.add("is-visible");
}
return true;
}
input.addEventListener("input", function () {
validateEmail(false);
});
fixButton.addEventListener("click", function () {
if (fixButton.dataset.suggestion) {
input.value = fixButton.dataset.suggestion;
validateEmail(true);
}
});
form.addEventListener("submit", function (event) {
event.preventDefault();
validateEmail(true);
});
});
})();
HTML
<div class="js-email-suggestion-preview" data-js-email-suggestion-demo>
<div class="js-email-suggestion-main">
<div class="js-email-suggestion-badge">Email Validation</div>
<h3>Catch email mistakes <span>before submit</span></h3>
<p class="js-email-suggestion-text">Try examples like <strong>john@gmail.con</strong>, <strong>anna@gnail.com</strong>, or a normal email address to see format validation and typo suggestions.</p>
<form class="js-email-suggestion-form" novalidate data-js-email-suggestion-form>
<div class="js-email-suggestion-field" data-js-email-field>
<label for="email-suggestion-input">Email address</label>
<div class="js-email-suggestion-input-row">
<div class="js-email-suggestion-input-wrap">
<input id="email-suggestion-input" name="email" type="email" placeholder="you@example.com" autocomplete="email">
<span class="js-email-suggestion-status" aria-hidden="true"></span>
</div>
<button class="js-email-suggestion-submit" type="submit">Validate Email</button>
</div>
<div class="js-email-suggestion-message" data-js-email-message></div>
<button class="js-email-suggestion-fix" type="button" data-js-email-fix></button>
</div>
<div class="js-email-suggestion-result" data-js-email-result>
Email validation passed. This address is ready to use in a real form submission.
</div>
</form>
</div>
<aside class="js-email-suggestion-side">
<div class="js-email-suggestion-envelope">
<div class="js-email-suggestion-envelope-icon">✉</div>
</div>
<div class="js-email-suggestion-side-content">
<h3>Better email capture</h3>
<p>Helpful validation reduces mistakes in lead forms, account registration, checkout, and newsletter signup flows.</p>
</div>
<ul class="js-email-suggestion-list">
<li>Checks email format</li>
<li>Suggests common domain fixes</li>
<li>Shows clear success and warning states</li>
</ul>
</aside>
</div>
CSS
.js-email-suggestion-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 24px;
align-items: stretch;
min-height: 560px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(14, 165, 233, 0.14), transparent 34%),
radial-gradient(circle at 88% 14%, rgba(99, 102, 241, 0.13), transparent 34%),
linear-gradient(135deg, #f0f9ff 0%, #ffffff 50%, #eef2ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-email-suggestion-main {
display: flex;
flex-direction: column;
justify-content: center;
padding: 34px;
border-radius: 30px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.12);
}
.js-email-suggestion-badge {
display: inline-flex;
width: fit-content;
align-items: center;
gap: 8px;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: #eff6ff;
border: 1px solid rgba(37, 99, 235, 0.16);
color: #1d4ed8;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.js-email-suggestion-main h3 {
max-width: 620px;
margin: 0 0 12px;
color: #0f172a;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.03;
font-weight: 950;
letter-spacing: -0.07em;
}
.js-email-suggestion-main h3 span {
background: linear-gradient(135deg, #0284c7, #4f46e5);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.js-email-suggestion-text {
max-width: 610px;
margin: 0 0 28px;
color: #64748b;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-email-suggestion-form {
display: grid;
gap: 16px;
max-width: 620px;
}
.js-email-suggestion-field {
display: grid;
gap: 8px;
}
.js-email-suggestion-field label {
color: #0f172a;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-email-suggestion-input-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 10px;
}
.js-email-suggestion-input-wrap {
position: relative;
}
.js-email-suggestion-input-wrap input {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 46px 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #0f172a;
font-size: 16px;
line-height: 1.4;
font-weight: 700;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-email-suggestion-input-wrap input:focus {
background: #ffffff;
border-color: rgba(37, 99, 235, 0.62);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
}
.js-email-suggestion-status {
position: absolute;
top: 50%;
right: 14px;
transform: translateY(-50%);
display: none;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 999px;
font-size: 13px;
font-weight: 950;
}
.js-email-suggestion-field.is-valid .js-email-suggestion-status,
.js-email-suggestion-field.is-error .js-email-suggestion-status,
.js-email-suggestion-field.is-warning .js-email-suggestion-status {
display: inline-flex;
}
.js-email-suggestion-field.is-valid .js-email-suggestion-status {
background: #dcfce7;
color: #15803d;
}
.js-email-suggestion-field.is-valid .js-email-suggestion-status::before {
content: "✓";
}
.js-email-suggestion-field.is-error .js-email-suggestion-status {
background: #fee2e2;
color: #b91c1c;
}
.js-email-suggestion-field.is-error .js-email-suggestion-status::before {
content: "!";
}
.js-email-suggestion-field.is-warning .js-email-suggestion-status {
background: #fef3c7;
color: #b45309;
}
.js-email-suggestion-field.is-warning .js-email-suggestion-status::before {
content: "?";
}
.js-email-suggestion-field.is-valid input {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-email-suggestion-field.is-error input {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-email-suggestion-field.is-warning input {
border-color: rgba(245, 158, 11, 0.68);
background: #fffbeb;
}
.js-email-suggestion-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 15px 20px;
border: 0;
border-radius: 18px;
background: linear-gradient(135deg, #0284c7, #4f46e5);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(37, 99, 235, 0.26);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-email-suggestion-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(37, 99, 235, 0.34);
}
.js-email-suggestion-message {
min-height: 19px;
color: #ef4444;
font-size: 13px;
line-height: 1.45;
font-weight: 750;
}
.js-email-suggestion-field.is-valid .js-email-suggestion-message {
color: #15803d;
}
.js-email-suggestion-field.is-warning .js-email-suggestion-message {
color: #b45309;
}
.js-email-suggestion-fix {
display: none;
width: fit-content;
padding: 9px 12px;
border: 0;
border-radius: 999px;
background: #fef3c7;
color: #92400e;
font-size: 13px;
line-height: 1;
font-weight: 900;
cursor: pointer;
}
.js-email-suggestion-fix.is-visible {
display: inline-flex;
}
.js-email-suggestion-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-email-suggestion-result.is-visible {
display: block;
}
.js-email-suggestion-side {
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
padding: 28px;
border-radius: 30px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.18), transparent 32%),
linear-gradient(135deg, #0f172a 0%, #1e3a8a 54%, #312e81 100%);
box-shadow: 0 26px 80px rgba(30, 64, 175, 0.25);
}
.js-email-suggestion-side::after {
content: "";
position: absolute;
right: -90px;
top: -90px;
width: 220px;
height: 220px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.11);
}
.js-email-suggestion-envelope {
position: relative;
z-index: 2;
display: grid;
place-items: center;
min-height: 190px;
border-radius: 26px;
background: rgba(255, 255, 255, 0.11);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.js-email-suggestion-envelope-icon {
display: grid;
place-items: center;
width: 92px;
height: 92px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.14);
color: #ffffff;
font-size: 46px;
}
.js-email-suggestion-side-content {
position: relative;
z-index: 2;
}
.js-email-suggestion-side-content h3 {
margin: 0 0 12px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 28px;
line-height: 1.12;
font-weight: 950;
letter-spacing: -0.045em;
}
.js-email-suggestion-side-content p {
margin: 0;
color: #dbeafe;
font-size: 14px;
line-height: 1.65;
font-weight: 650;
}
.js-email-suggestion-list {
position: relative;
z-index: 2;
display: grid;
gap: 10px;
margin: 0;
padding: 0;
list-style: none;
}
.js-email-suggestion-list li {
display: flex;
align-items: center;
gap: 9px;
color: #eff6ff;
font-size: 13px;
line-height: 1.4;
font-weight: 800;
}
.js-email-suggestion-list li::before {
content: "✓";
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 21px;
height: 21px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
color: #bfdbfe;
font-size: 12px;
font-weight: 950;
}
@media (max-width: 900px) {
.js-email-suggestion-preview {
grid-template-columns: 1fr;
}
.js-email-suggestion-side {
min-height: 360px;
}
}
@media (max-width: 640px) {
.js-email-suggestion-preview {
padding: 18px;
border-radius: 24px;
}
.js-email-suggestion-main,
.js-email-suggestion-side {
padding: 24px;
border-radius: 24px;
}
.js-email-suggestion-input-row {
grid-template-columns: 1fr;
}
.js-email-suggestion-main h3 {
font-size: 34px;
}
.js-email-suggestion-submit {
width: 100%;
}
}
This email validation example is useful when an accurate email address is important for the user journey. You can use the same idea in newsletter forms, lead magnets, user registration, checkout pages, demo request forms, and account settings screens.
6. Login Form Validation with Error Animation
Login form validation should be simple, fast, and clear. A login form usually needs only a valid email address, a password field, and optional controls such as “remember me”. The goal is to help users correct missing or invalid fields without making the login screen feel complicated.
This example uses a compact dark login card with email validation, password length validation, remember me checkbox styling, a general error banner, and a shake animation when validation fails. It works well for dashboards, member portals, SaaS apps, ecommerce accounts, admin areas, and private client portals.
- Compact JavaScript login form validation
- Validates email format and password length
- Includes general error banner and inline field messages
- Uses a shake animation for invalid submit attempts
- Good for dashboards, account pages, SaaS login screens, and member portals
Validate login fields without slowing users down
A login form should catch missing or invalid input quickly while keeping the interface focused, compact, and easy to use.
Welcome back
Sign in to continue to your dashboard.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-login-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-login-validation-form]");
const card = demo.querySelector("[data-js-login-card]");
const alertBox = demo.querySelector("[data-js-login-alert]");
const successBox = demo.querySelector("[data-js-login-success]");
const fields = {
email: form.querySelector("[name='email']"),
password: form.querySelector("[name='password']")
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-login-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-login-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function validateEmail() {
const value = fields.email.value.trim();
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(value)) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validatePassword() {
const value = fields.password.value;
if (value.length < 6) {
setField("password", false, "Password must be at least 6 characters.");
return false;
}
setField("password", true, "Password length looks good.");
return true;
}
function shakeCard() {
card.classList.remove("is-shaking");
window.setTimeout(function () {
card.classList.add("is-shaking");
}, 10);
window.setTimeout(function () {
card.classList.remove("is-shaking");
}, 430);
}
function clearGlobalMessages() {
alertBox.classList.remove("is-visible");
successBox.classList.remove("is-visible");
}
fields.email.addEventListener("input", function () {
validateEmail();
clearGlobalMessages();
});
fields.password.addEventListener("input", function () {
validatePassword();
clearGlobalMessages();
});
form.addEventListener("submit", function (event) {
event.preventDefault();
const isEmailValid = validateEmail();
const isPasswordValid = validatePassword();
if (isEmailValid && isPasswordValid) {
alertBox.classList.remove("is-visible");
successBox.classList.add("is-visible");
} else {
successBox.classList.remove("is-visible");
alertBox.classList.add("is-visible");
shakeCard();
}
});
});
})();
HTML
<div class="js-login-validation-preview" data-js-login-validation-demo>
<div class="js-login-validation-orb one"></div>
<div class="js-login-validation-orb two"></div>
<div class="js-login-validation-shell">
<div class="js-login-validation-copy">
<div class="js-login-validation-kicker">Login Validation</div>
<h3>Validate login fields without slowing users down</h3>
<p>A login form should catch missing or invalid input quickly while keeping the interface focused, compact, and easy to use.</p>
<div class="js-login-validation-features">
<div class="js-login-validation-feature">
<strong>Email format</strong>
<span>Checks email before submit</span>
</div>
<div class="js-login-validation-feature">
<strong>Password rule</strong>
<span>Requires minimum length</span>
</div>
<div class="js-login-validation-feature">
<strong>Error motion</strong>
<span>Shake animation on invalid submit</span>
</div>
</div>
</div>
<div class="js-login-validation-card" data-js-login-card>
<div class="js-login-validation-card-head">
<div class="js-login-validation-avatar">↳</div>
<div>
<h3>Welcome back</h3>
<p>Sign in to continue to your dashboard.</p>
</div>
</div>
<div class="js-login-validation-alert" data-js-login-alert>
Please fix the highlighted fields before signing in.
</div>
<div class="js-login-validation-success" data-js-login-success>
Login form validation passed. This is a front-end demo only.
</div>
<form class="js-login-validation-form" novalidate data-js-login-validation-form>
<div class="js-login-validation-field" data-login-field="email">
<label for="login-validation-email">Email address</label>
<input id="login-validation-email" name="email" type="email" placeholder="you@example.com" autocomplete="email">
<div class="js-login-validation-message" data-login-message="email"></div>
</div>
<div class="js-login-validation-field" data-login-field="password">
<label for="login-validation-password">Password</label>
<input id="login-validation-password" name="password" type="password" placeholder="Enter at least 6 characters" autocomplete="current-password">
<div class="js-login-validation-message" data-login-message="password"></div>
</div>
<div class="js-login-validation-options">
<label class="js-login-validation-remember">
<input name="remember" type="checkbox">
<span>Remember me</span>
</label>
<a class="js-login-validation-link" href="#">Forgot password?</a>
</div>
<button class="js-login-validation-submit" type="submit">Sign In</button>
<p class="js-login-validation-note">Demo tip: use a valid email format and a password with at least 6 characters.</p>
</form>
</div>
</div>
</div>
CSS
.js-login-validation-preview {
position: relative;
display: grid;
place-items: center;
min-height: 660px;
padding: 34px;
border-radius: 28px;
overflow: hidden;
background:
radial-gradient(circle at 18% 16%, rgba(59, 130, 246, 0.22), transparent 34%),
radial-gradient(circle at 82% 18%, rgba(236, 72, 153, 0.18), transparent 34%),
radial-gradient(circle at 50% 90%, rgba(20, 184, 166, 0.14), transparent 36%),
linear-gradient(135deg, #020617 0%, #111827 54%, #1e1b4b 100%);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.js-login-validation-orb {
position: absolute;
border-radius: 999px;
filter: blur(2px);
opacity: 0.85;
}
.js-login-validation-orb.one {
left: 8%;
top: 12%;
width: 90px;
height: 90px;
background: rgba(59, 130, 246, 0.28);
}
.js-login-validation-orb.two {
right: 9%;
bottom: 14%;
width: 120px;
height: 120px;
background: rgba(236, 72, 153, 0.22);
}
.js-login-validation-shell {
position: relative;
z-index: 2;
width: min(100%, 1040px);
display: grid;
grid-template-columns: minmax(0, 1fr) 420px;
gap: 26px;
align-items: center;
}
.js-login-validation-copy {
padding: 34px;
}
.js-login-validation-kicker {
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(96, 165, 250, 0.13);
border: 1px solid rgba(96, 165, 250, 0.24);
color: #bfdbfe;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-login-validation-copy h3 {
max-width: 560px;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(38px, 6vw, 72px);
line-height: 0.98;
font-weight: 950;
letter-spacing: -0.08em;
}
.js-login-validation-copy p {
max-width: 520px;
margin: 0;
color: #cbd5e1;
font-size: 17px;
line-height: 1.75;
font-weight: 650;
}
.js-login-validation-features {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
max-width: 600px;
margin-top: 28px;
}
.js-login-validation-feature {
padding: 14px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.10);
}
.js-login-validation-feature strong {
display: block;
color: #ffffff;
font-size: 13px;
font-weight: 950;
line-height: 1.2;
}
.js-login-validation-feature span {
display: block;
margin-top: 5px;
color: #cbd5e1;
font-size: 12px;
line-height: 1.4;
font-weight: 650;
}
.js-login-validation-card {
padding: 28px;
border-radius: 32px;
background: rgba(15, 23, 42, 0.76);
border: 1px solid rgba(255, 255, 255, 0.13);
box-shadow: 0 30px 90px rgba(0, 0, 0, 0.35);
backdrop-filter: blur(18px);
}
.js-login-validation-card.is-shaking {
animation: jsLoginShake 0.38s ease both;
}
@keyframes jsLoginShake {
0%, 100% {
transform: translateX(0);
}
20% {
transform: translateX(-10px);
}
40% {
transform: translateX(8px);
}
60% {
transform: translateX(-6px);
}
80% {
transform: translateX(4px);
}
}
.js-login-validation-card-head {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 22px;
}
.js-login-validation-avatar {
display: grid;
place-items: center;
width: 58px;
height: 58px;
border-radius: 20px;
background: linear-gradient(135deg, #3b82f6, #ec4899);
color: #ffffff;
font-size: 26px;
font-weight: 950;
box-shadow: 0 18px 44px rgba(59, 130, 246, 0.28);
}
.js-login-validation-card-head h3 {
margin: 0 0 5px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 28px;
line-height: 1.1;
font-weight: 950;
letter-spacing: -0.045em;
}
.js-login-validation-card-head p {
margin: 0;
color: #cbd5e1;
font-size: 13px;
line-height: 1.4;
font-weight: 650;
}
.js-login-validation-alert {
display: none;
margin-bottom: 16px;
padding: 13px 14px;
border-radius: 16px;
background: rgba(239, 68, 68, 0.13);
border: 1px solid rgba(239, 68, 68, 0.24);
color: #fecaca;
font-size: 13px;
line-height: 1.45;
font-weight: 800;
}
.js-login-validation-alert.is-visible {
display: block;
}
.js-login-validation-success {
display: none;
margin-bottom: 16px;
padding: 13px 14px;
border-radius: 16px;
background: rgba(16, 185, 129, 0.13);
border: 1px solid rgba(16, 185, 129, 0.24);
color: #a7f3d0;
font-size: 13px;
line-height: 1.45;
font-weight: 800;
}
.js-login-validation-success.is-visible {
display: block;
}
.js-login-validation-form {
display: grid;
gap: 16px;
}
.js-login-validation-field {
display: grid;
gap: 8px;
}
.js-login-validation-field label {
color: #f8fafc;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.03em;
}
.js-login-validation-field input {
width: 100%;
box-sizing: border-box;
min-height: 52px;
padding: 14px 15px;
border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 17px;
background: rgba(2, 6, 23, 0.62);
color: #ffffff;
font-size: 15px;
line-height: 1.4;
font-weight: 700;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-login-validation-field input::placeholder {
color: rgba(203, 213, 225, 0.68);
}
.js-login-validation-field input:focus {
border-color: rgba(96, 165, 250, 0.66);
background: rgba(2, 6, 23, 0.84);
box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.12);
}
.js-login-validation-field.is-error input {
border-color: rgba(248, 113, 113, 0.68);
box-shadow: 0 0 0 4px rgba(248, 113, 113, 0.10);
}
.js-login-validation-field.is-valid input {
border-color: rgba(52, 211, 153, 0.62);
}
.js-login-validation-message {
min-height: 18px;
color: #fca5a5;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-login-validation-field.is-valid .js-login-validation-message {
color: #86efac;
}
.js-login-validation-options {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
flex-wrap: wrap;
}
.js-login-validation-remember {
display: inline-flex;
align-items: center;
gap: 9px;
color: #cbd5e1;
font-size: 13px;
line-height: 1.3;
font-weight: 750;
}
.js-login-validation-remember input {
width: 17px;
height: 17px;
accent-color: #3b82f6;
}
.js-login-validation-link {
color: #bfdbfe !important;
-webkit-text-fill-color: #bfdbfe !important;
font-size: 13px;
line-height: 1.3;
font-weight: 850;
text-decoration: none !important;
}
.js-login-validation-link:hover {
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
}
.js-login-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #3b82f6, #ec4899);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 20px 48px rgba(59, 130, 246, 0.28);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-login-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 24px 58px rgba(59, 130, 246, 0.38);
}
.js-login-validation-note {
margin: 2px 0 0;
color: #94a3b8;
font-size: 12px;
line-height: 1.55;
font-weight: 650;
}
@media (max-width: 940px) {
.js-login-validation-shell {
grid-template-columns: 1fr;
}
.js-login-validation-copy {
padding: 20px 8px 0;
}
}
@media (max-width: 640px) {
.js-login-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-login-validation-copy h3 {
font-size: 38px;
}
.js-login-validation-features {
grid-template-columns: 1fr;
}
.js-login-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-login-validation-options {
align-items: flex-start;
flex-direction: column;
}
}
This login form validation example keeps the validation rules simple while making the feedback visible. It is a good pattern for dashboards, SaaS apps, account login pages, ecommerce customer accounts, private portals, and admin login screens.
7. Newsletter Signup Validation
Newsletter signup validation is a simple but important pattern for blogs, landing pages, ecommerce stores, SaaS websites, and content platforms. A good newsletter form should check the email address, confirm consent when needed, and show a clear success message without making the subscription flow feel heavy.
This example uses a modern editorial-style newsletter section with email validation, privacy checkbox validation, duplicate-style message simulation, and a polished success state. The design is intentionally different from the previous login and email examples, with a magazine-style layout and a large call-to-action panel.
- Newsletter email validation with JavaScript
- Validates email format and privacy consent
- Includes a duplicate-style warning for a demo email
- Shows a clear subscription success message
- Good for blogs, content websites, lead magnets, SaaS updates, and ecommerce newsletters
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-newsletter-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-newsletter-validation-form]");
const emailInput = form.querySelector("[name='email']");
const privacyInput = form.querySelector("[name='privacy']");
const emailField = demo.querySelector("[data-newsletter-field='email']");
const emailMessage = demo.querySelector("[data-newsletter-message='email']");
const privacyMessage = demo.querySelector("[data-newsletter-message='privacy']");
const result = demo.querySelector("[data-js-newsletter-result]");
function setEmailState(state, message) {
emailField.classList.remove("is-valid", "is-error", "is-warning");
emailField.classList.add(state);
emailMessage.textContent = message;
}
function validateEmail() {
const value = emailInput.value.trim().toLowerCase();
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!value) {
setEmailState("is-error", "Please enter your email address.");
return false;
}
if (!pattern.test(value)) {
setEmailState("is-error", "Please enter a valid email address.");
return false;
}
if (value === "demo@example.com") {
setEmailState("is-warning", "This email is already subscribed in this demo.");
return false;
}
setEmailState("is-valid", "Email looks good.");
return true;
}
function validatePrivacy() {
if (!privacyInput.checked) {
privacyMessage.textContent = "Please accept the newsletter privacy notice.";
privacyMessage.style.color = "#dc2626";
return false;
}
privacyMessage.textContent = "Privacy notice accepted.";
privacyMessage.style.color = "#15803d";
return true;
}
function clearResult() {
result.classList.remove("is-visible");
}
emailInput.addEventListener("input", function () {
validateEmail();
clearResult();
});
privacyInput.addEventListener("change", function () {
validatePrivacy();
clearResult();
});
form.addEventListener("submit", function (event) {
event.preventDefault();
const isEmailValid = validateEmail();
const isPrivacyValid = validatePrivacy();
if (isEmailValid && isPrivacyValid) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
});
})();
HTML
<div class="js-newsletter-validation-preview" data-js-newsletter-validation-demo>
<div class="js-newsletter-validation-magazine">
<div>
<div class="js-newsletter-validation-label">Weekly Dispatch</div>
<h3>Get better web design ideas every week</h3>
<p>Validate newsletter email addresses before storing them in your email marketing system or CRM.</p>
</div>
<div class="js-newsletter-validation-stats">
<div class="js-newsletter-validation-stat">
<strong>30k</strong>
<span>Monthly readers</span>
</div>
<div class="js-newsletter-validation-stat">
<strong>1x</strong>
<span>Email per week</span>
</div>
<div class="js-newsletter-validation-stat">
<strong>0</strong>
<span>Spam promises</span>
</div>
</div>
</div>
<div class="js-newsletter-validation-card">
<h3>Join the newsletter</h3>
<p class="js-newsletter-validation-card-text">Try submitting an empty form, a wrong email, <strong>demo@example.com</strong>, or a valid new email address.</p>
<form class="js-newsletter-validation-form" novalidate data-js-newsletter-validation-form>
<div class="js-newsletter-validation-field" data-newsletter-field="email">
<label for="newsletter-validation-email">Email address</label>
<div class="js-newsletter-validation-input-wrap">
<input id="newsletter-validation-email" name="email" type="email" placeholder="you@example.com">
<span class="js-newsletter-validation-icon" aria-hidden="true"></span>
</div>
<div class="js-newsletter-validation-message" data-newsletter-message="email"></div>
</div>
<div class="js-newsletter-validation-privacy">
<input id="newsletter-validation-privacy" name="privacy" type="checkbox">
<label for="newsletter-validation-privacy">I agree to receive email updates and understand I can unsubscribe at any time.</label>
<div class="js-newsletter-validation-privacy-message" data-newsletter-message="privacy"></div>
</div>
<button class="js-newsletter-validation-submit" type="submit">Subscribe Now</button>
<div class="js-newsletter-validation-result" data-js-newsletter-result>
Subscription validation passed. This demo can now be connected to Mailchimp, ConvertKit, Brevo, or your own backend.
</div>
</form>
</div>
</div>
CSS
.js-newsletter-validation-preview {
display: grid;
grid-template-columns: minmax(0, 0.92fr) minmax(340px, 1.08fr);
gap: 28px;
align-items: stretch;
min-height: 580px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 14% 16%, rgba(245, 158, 11, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(217, 70, 239, 0.13), transparent 34%),
linear-gradient(135deg, #fffbeb 0%, #ffffff 52%, #fdf4ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-newsletter-validation-magazine {
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 28px;
padding: 34px;
border-radius: 30px;
background:
radial-gradient(circle at 24% 20%, rgba(255, 255, 255, 0.18), transparent 34%),
linear-gradient(135deg, #78350f 0%, #b45309 48%, #a21caf 100%);
box-shadow: 0 26px 80px rgba(180, 83, 9, 0.24);
}
.js-newsletter-validation-magazine::before {
content: "";
position: absolute;
right: -120px;
top: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-newsletter-validation-label {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #fef3c7;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-newsletter-validation-magazine h3 {
position: relative;
z-index: 2;
max-width: 420px;
margin: 0;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(38px, 5vw, 64px);
line-height: 0.98;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-newsletter-validation-magazine p {
position: relative;
z-index: 2;
max-width: 430px;
margin: 18px 0 0;
color: #ffedd5;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-newsletter-validation-stats {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.js-newsletter-validation-stat {
padding: 15px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-newsletter-validation-stat strong {
display: block;
margin-bottom: 4px;
color: #ffffff;
font-size: 24px;
line-height: 1;
font-weight: 950;
}
.js-newsletter-validation-stat span {
display: block;
color: #ffedd5;
font-size: 12px;
line-height: 1.35;
font-weight: 750;
}
.js-newsletter-validation-card {
display: flex;
flex-direction: column;
justify-content: center;
padding: 36px;
border-radius: 30px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.12);
}
.js-newsletter-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-newsletter-validation-card-text {
margin: 0 0 24px;
color: #64748b;
font-size: 16px;
line-height: 1.7;
font-weight: 650;
}
.js-newsletter-validation-form {
display: grid;
gap: 16px;
}
.js-newsletter-validation-field {
display: grid;
gap: 8px;
}
.js-newsletter-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-newsletter-validation-input-wrap {
position: relative;
}
.js-newsletter-validation-field input[type="email"] {
width: 100%;
box-sizing: border-box;
min-height: 56px;
padding: 15px 48px 15px 16px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 20px;
background: #f8fafc;
color: #111827;
font-size: 16px;
line-height: 1.4;
font-weight: 700;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-newsletter-validation-field input[type="email"]:focus {
background: #ffffff;
border-color: rgba(217, 70, 239, 0.62);
box-shadow: 0 0 0 4px rgba(217, 70, 239, 0.11);
}
.js-newsletter-validation-icon {
position: absolute;
top: 50%;
right: 15px;
transform: translateY(-50%);
display: none;
align-items: center;
justify-content: center;
width: 25px;
height: 25px;
border-radius: 999px;
font-size: 13px;
font-weight: 950;
}
.js-newsletter-validation-field.is-valid .js-newsletter-validation-icon,
.js-newsletter-validation-field.is-error .js-newsletter-validation-icon,
.js-newsletter-validation-field.is-warning .js-newsletter-validation-icon {
display: inline-flex;
}
.js-newsletter-validation-field.is-valid .js-newsletter-validation-icon {
background: #dcfce7;
color: #15803d;
}
.js-newsletter-validation-field.is-valid .js-newsletter-validation-icon::before {
content: "✓";
}
.js-newsletter-validation-field.is-error .js-newsletter-validation-icon {
background: #fee2e2;
color: #b91c1c;
}
.js-newsletter-validation-field.is-error .js-newsletter-validation-icon::before {
content: "!";
}
.js-newsletter-validation-field.is-warning .js-newsletter-validation-icon {
background: #fef3c7;
color: #b45309;
}
.js-newsletter-validation-field.is-warning .js-newsletter-validation-icon::before {
content: "!";
}
.js-newsletter-validation-field.is-valid input[type="email"] {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-newsletter-validation-field.is-error input[type="email"] {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-newsletter-validation-field.is-warning input[type="email"] {
border-color: rgba(245, 158, 11, 0.68);
background: #fffbeb;
}
.js-newsletter-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.4;
font-weight: 750;
}
.js-newsletter-validation-field.is-valid .js-newsletter-validation-message {
color: #15803d;
}
.js-newsletter-validation-field.is-warning .js-newsletter-validation-message {
color: #b45309;
}
.js-newsletter-validation-privacy {
display: grid;
grid-template-columns: 20px 1fr;
gap: 10px;
padding: 14px;
border-radius: 18px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-newsletter-validation-privacy input {
width: 18px;
height: 18px;
margin-top: 2px;
accent-color: #c026d3;
}
.js-newsletter-validation-privacy label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 700;
}
.js-newsletter-validation-privacy-message {
grid-column: 1 / -1;
min-height: 16px;
color: #dc2626;
font-size: 13px;
font-weight: 750;
}
.js-newsletter-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #f59e0b, #d946ef);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(217, 70, 239, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-newsletter-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(217, 70, 239, 0.34);
}
.js-newsletter-validation-result {
display: none;
padding: 15px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-newsletter-validation-result.is-visible {
display: block;
}
@media (max-width: 920px) {
.js-newsletter-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.js-newsletter-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-newsletter-validation-magazine,
.js-newsletter-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-newsletter-validation-stats {
grid-template-columns: 1fr;
}
.js-newsletter-validation-magazine h3,
.js-newsletter-validation-card h3 {
font-size: 32px;
}
}
This newsletter signup validation example is a practical pattern for blogs, landing pages, lead magnets, ecommerce stores, SaaS product updates, and content websites. In production, connect the validated form to your email marketing provider or backend subscription endpoint.
8. Multi-Step Form Validation
Multi-step form validation is useful when a long form would feel overwhelming on one screen. Instead of showing every field at once, this pattern breaks the form into smaller steps and validates each step before the user can move forward.
This example uses a three-step project request wizard with progress indicators, step-specific validation, back and next buttons, and a final success message. It is useful for quote request forms, onboarding forms, booking forms, application forms, lead qualification forms, and service inquiry flows.
- Three-step JavaScript form validation wizard
- Validates each step before moving forward
- Includes progress indicators and active step states
- Uses next, back, and submit controls
- Good for quote forms, onboarding forms, booking forms, and project request forms
Contact details
Start with your name and email address.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-multistep-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-multistep-form]");
const pages = Array.from(demo.querySelectorAll("[data-step-page]"));
const navItems = Array.from(demo.querySelectorAll("[data-step-nav]"));
const title = demo.querySelector("[data-js-multistep-title]");
const description = demo.querySelector("[data-js-multistep-description]");
const progress = demo.querySelector("[data-js-multistep-progress]");
const backButton = demo.querySelector("[data-js-multistep-back]");
const nextButton = demo.querySelector("[data-js-multistep-next]");
const submitButton = demo.querySelector("[data-js-multistep-submit]");
const result = demo.querySelector("[data-js-multistep-result]");
let currentStep = 0;
const stepContent = [
{
title: "Contact details",
description: "Start with your name and email address."
},
{
title: "Project type",
description: "Choose what you want to build and select a budget range."
},
{
title: "Project details",
description: "Add a short message so the request has enough context."
}
];
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
projectType: form.querySelectorAll("[name='projectType']"),
budget: form.querySelector("[name='budget']"),
message: form.querySelector("[name='message']")
};
function setFieldState(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-multistep-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-multistep-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#047857" : "#dc2626";
}
}
function getSelectedProjectType() {
return Array.from(fields.projectType).find(function (input) {
return input.checked;
});
}
function validateStepOne() {
let valid = true;
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (fields.name.value.trim().length < 2) {
setFieldState("name", false, "Enter your full name.");
valid = false;
} else {
setFieldState("name", true, "Name looks good.");
}
if (!emailPattern.test(fields.email.value.trim())) {
setFieldState("email", false, "Enter a valid email address.");
valid = false;
} else {
setFieldState("email", true, "Email looks valid.");
}
return valid;
}
function validateStepTwo() {
let valid = true;
const projectMessage = demo.querySelector("[data-multistep-message='projectType']");
const selectedType = getSelectedProjectType();
if (!selectedType) {
projectMessage.textContent = "Choose one project type.";
projectMessage.style.color = "#dc2626";
valid = false;
} else {
projectMessage.textContent = "Project type selected.";
projectMessage.style.color = "#047857";
}
if (!fields.budget.value) {
setFieldState("budget", false, "Choose a budget range.");
valid = false;
} else {
setFieldState("budget", true, "Budget range selected.");
}
return valid;
}
function validateStepThree() {
if (fields.message.value.trim().length < 30) {
setFieldState("message", false, "Write at least 30 characters.");
return false;
}
setFieldState("message", true, "Project details look good.");
return true;
}
function validateCurrentStep() {
if (currentStep === 0) {
return validateStepOne();
}
if (currentStep === 1) {
return validateStepTwo();
}
return validateStepThree();
}
function updateStep() {
pages.forEach(function (page, index) {
page.classList.toggle("is-active", index === currentStep);
});
navItems.forEach(function (item, index) {
item.classList.toggle("is-active", index === currentStep);
item.classList.toggle("is-complete", index < currentStep);
});
title.textContent = stepContent[currentStep].title;
description.textContent = stepContent[currentStep].description;
progress.style.width = ((currentStep + 1) / pages.length * 100) + "%";
backButton.hidden = currentStep === 0;
nextButton.hidden = currentStep === pages.length - 1;
submitButton.hidden = currentStep !== pages.length - 1;
result.classList.remove("is-visible");
}
nextButton.addEventListener("click", function () {
if (validateCurrentStep()) {
currentStep++;
updateStep();
}
});
backButton.addEventListener("click", function () {
currentStep--;
updateStep();
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateStepOne() && validateStepTwo() && validateStepThree()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
fields.name.addEventListener("input", function () {
if (currentStep === 0) {
validateStepOne();
}
});
fields.email.addEventListener("input", function () {
if (currentStep === 0) {
validateStepOne();
}
});
fields.projectType.forEach(function (input) {
input.addEventListener("change", function () {
if (currentStep === 1) {
validateStepTwo();
}
});
});
fields.budget.addEventListener("change", function () {
if (currentStep === 1) {
validateStepTwo();
}
});
fields.message.addEventListener("input", function () {
if (currentStep === 2) {
validateStepThree();
}
});
updateStep();
});
})();
HTML
<div class="js-multistep-validation-preview" data-js-multistep-validation-demo>
<div class="js-multistep-validation-shell">
<aside class="js-multistep-validation-sidebar">
<div class="js-multistep-validation-sidebar-content">
<div class="js-multistep-validation-kicker">Project Wizard</div>
<h3>Validate one step at a time</h3>
<p>Break long forms into smaller sections and only let users continue when the current step is complete.</p>
</div>
<div class="js-multistep-validation-steps">
<div class="js-multistep-validation-step-nav is-active" data-step-nav="0">
<div class="js-multistep-validation-step-number">1</div>
<div>
<strong>Contact</strong>
<span>Name and email</span>
</div>
</div>
<div class="js-multistep-validation-step-nav" data-step-nav="1">
<div class="js-multistep-validation-step-number">2</div>
<div>
<strong>Project</strong>
<span>Type and budget</span>
</div>
</div>
<div class="js-multistep-validation-step-nav" data-step-nav="2">
<div class="js-multistep-validation-step-number">3</div>
<div>
<strong>Details</strong>
<span>Message and submit</span>
</div>
</div>
</div>
</aside>
<div class="js-multistep-validation-card">
<div class="js-multistep-validation-head">
<h3 data-js-multistep-title>Contact details</h3>
<p data-js-multistep-description>Start with your name and email address.</p>
</div>
<div class="js-multistep-validation-progress">
<div class="js-multistep-validation-progress-fill" data-js-multistep-progress></div>
</div>
<form class="js-multistep-validation-form" novalidate data-js-multistep-form>
<div class="js-multistep-validation-page is-active" data-step-page="0">
<div class="js-multistep-validation-grid">
<div class="js-multistep-validation-field" data-multistep-field="name">
<label for="multistep-name">Full name</label>
<input id="multistep-name" name="name" type="text" placeholder="Your name">
<div class="js-multistep-validation-message" data-multistep-message="name"></div>
</div>
<div class="js-multistep-validation-field" data-multistep-field="email">
<label for="multistep-email">Email address</label>
<input id="multistep-email" name="email" type="email" placeholder="you@example.com">
<div class="js-multistep-validation-message" data-multistep-message="email"></div>
</div>
</div>
</div>
<div class="js-multistep-validation-page" data-step-page="1">
<div class="js-multistep-validation-choice-grid">
<label class="js-multistep-validation-choice">
<input name="projectType" type="radio" value="Website">
<span data-icon="🌐">Website project</span>
</label>
<label class="js-multistep-validation-choice">
<input name="projectType" type="radio" value="Ecommerce">
<span data-icon="🛒">Ecommerce store</span>
</label>
<label class="js-multistep-validation-choice">
<input name="projectType" type="radio" value="App">
<span data-icon="⚙">Web app or tool</span>
</label>
</div>
<div class="js-multistep-validation-choice-message" data-multistep-message="projectType"></div>
<div class="js-multistep-validation-field" data-multistep-field="budget">
<label for="multistep-budget">Estimated budget</label>
<select id="multistep-budget" name="budget">
<option value="">Choose a budget range</option>
<option value="Under $1,000">Under $1,000</option>
<option value="$1,000 - $3,000">$1,000 - $3,000</option>
<option value="$3,000 - $10,000">$3,000 - $10,000</option>
<option value="$10,000+">$10,000+</option>
</select>
<div class="js-multistep-validation-message" data-multistep-message="budget"></div>
</div>
</div>
<div class="js-multistep-validation-page" data-step-page="2">
<div class="js-multistep-validation-field" data-multistep-field="message">
<label for="multistep-message">Project details</label>
<textarea id="multistep-message" name="message" placeholder="Tell us what you want to build..."></textarea>
<div class="js-multistep-validation-message" data-multistep-message="message"></div>
</div>
</div>
<div class="js-multistep-validation-actions">
<button class="js-multistep-validation-back" type="button" data-js-multistep-back hidden>Back</button>
<button class="js-multistep-validation-next" type="button" data-js-multistep-next>Next Step</button>
<button class="js-multistep-validation-submit" type="submit" data-js-multistep-submit hidden>Submit Request</button>
</div>
<div class="js-multistep-validation-result" data-js-multistep-result>
Multi-step validation passed. This project request demo can now be connected to your CRM, email system, or backend form handler.
</div>
</form>
</div>
</div>
</div>
CSS
.js-multistep-validation-preview {
position: relative;
min-height: 700px;
padding: 34px;
border-radius: 28px;
overflow: hidden;
background:
radial-gradient(circle at 12% 16%, rgba(16, 185, 129, 0.16), transparent 34%),
radial-gradient(circle at 90% 18%, rgba(59, 130, 246, 0.15), transparent 34%),
linear-gradient(135deg, #ecfdf5 0%, #ffffff 50%, #eff6ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-multistep-validation-shell {
width: min(100%, 1060px);
margin: 0 auto;
display: grid;
grid-template-columns: 310px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
}
.js-multistep-validation-sidebar {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 20% 18%, rgba(255, 255, 255, 0.18), transparent 34%),
linear-gradient(135deg, #064e3b 0%, #047857 48%, #1d4ed8 100%);
box-shadow: 0 26px 80px rgba(4, 120, 87, 0.24);
}
.js-multistep-validation-sidebar::before {
content: "";
position: absolute;
right: -100px;
bottom: -100px;
width: 240px;
height: 240px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-multistep-validation-sidebar-content {
position: relative;
z-index: 2;
}
.js-multistep-validation-kicker {
display: inline-flex;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #d1fae5;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-multistep-validation-sidebar h3 {
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 36px;
line-height: 1.05;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-multistep-validation-sidebar p {
margin: 0 0 28px;
color: #dbeafe;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-multistep-validation-steps {
position: relative;
z-index: 2;
display: grid;
gap: 14px;
}
.js-multistep-validation-step-nav {
display: grid;
grid-template-columns: 38px 1fr;
gap: 12px;
align-items: center;
padding: 13px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.11);
border: 1px solid rgba(255, 255, 255, 0.13);
opacity: 0.72;
transition: opacity 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.js-multistep-validation-step-nav.is-active,
.js-multistep-validation-step-nav.is-complete {
opacity: 1;
background: rgba(255, 255, 255, 0.16);
border-color: rgba(255, 255, 255, 0.20);
}
.js-multistep-validation-step-number {
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.14);
color: #ffffff;
font-size: 14px;
font-weight: 950;
}
.js-multistep-validation-step-nav.is-complete .js-multistep-validation-step-number {
background: #dcfce7;
color: #047857;
}
.js-multistep-validation-step-nav.is-complete .js-multistep-validation-step-number::before {
content: "✓";
}
.js-multistep-validation-step-nav.is-complete .js-multistep-validation-step-number {
font-size: 0;
}
.js-multistep-validation-step-nav strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 14px;
line-height: 1.2;
font-weight: 950;
}
.js-multistep-validation-step-nav span {
display: block;
color: #dbeafe;
font-size: 12px;
line-height: 1.3;
font-weight: 650;
}
.js-multistep-validation-card {
padding: 34px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-multistep-validation-head {
margin-bottom: 26px;
}
.js-multistep-validation-head h3 {
margin: 0 0 10px;
color: #111827;
font-size: 38px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-multistep-validation-head p {
margin: 0;
color: #64748b;
font-size: 16px;
line-height: 1.7;
font-weight: 650;
}
.js-multistep-validation-progress {
height: 9px;
overflow: hidden;
margin-bottom: 26px;
border-radius: 999px;
background: #e2e8f0;
}
.js-multistep-validation-progress-fill {
width: 33.33%;
height: 100%;
border-radius: 999px;
background: linear-gradient(135deg, #10b981, #2563eb);
transition: width 0.25s ease;
}
.js-multistep-validation-form {
display: grid;
gap: 22px;
}
.js-multistep-validation-page {
display: none;
gap: 16px;
}
.js-multistep-validation-page.is-active {
display: grid;
}
.js-multistep-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-multistep-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-multistep-validation-field {
display: grid;
gap: 7px;
}
.js-multistep-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-multistep-validation-field input,
.js-multistep-validation-field select,
.js-multistep-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 52px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 17px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-multistep-validation-field textarea {
min-height: 128px;
resize: vertical;
}
.js-multistep-validation-field input:focus,
.js-multistep-validation-field select:focus,
.js-multistep-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(16, 185, 129, 0.62);
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.12);
}
.js-multistep-validation-field.is-error input,
.js-multistep-validation-field.is-error select,
.js-multistep-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-multistep-validation-field.is-valid input,
.js-multistep-validation-field.is-valid select,
.js-multistep-validation-field.is-valid textarea {
border-color: rgba(16, 185, 129, 0.58);
background: #f0fdf4;
}
.js-multistep-validation-message {
min-height: 17px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-multistep-validation-field.is-valid .js-multistep-validation-message {
color: #047857;
}
.js-multistep-validation-choice-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.js-multistep-validation-choice {
position: relative;
}
.js-multistep-validation-choice input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.js-multistep-validation-choice span {
display: grid;
gap: 8px;
min-height: 110px;
padding: 18px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.10);
color: #334155;
font-size: 14px;
line-height: 1.35;
font-weight: 850;
cursor: pointer;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.js-multistep-validation-choice span::before {
content: attr(data-icon);
display: grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: 14px;
background: #ecfdf5;
color: #047857;
font-size: 18px;
}
.js-multistep-validation-choice input:checked + span {
transform: translateY(-2px);
border-color: rgba(16, 185, 129, 0.62);
background: #ecfdf5;
box-shadow: 0 16px 34px rgba(16, 185, 129, 0.14);
}
.js-multistep-validation-choice-message {
min-height: 17px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-multistep-validation-actions {
display: flex;
justify-content: space-between;
gap: 12px;
padding-top: 4px;
}
.js-multistep-validation-back,
.js-multistep-validation-next,
.js-multistep-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 52px;
padding: 15px 20px;
border: 0;
border-radius: 999px;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-multistep-validation-back {
background: #e2e8f0;
color: #334155;
}
.js-multistep-validation-next,
.js-multistep-validation-submit {
background: linear-gradient(135deg, #10b981, #2563eb);
color: #ffffff;
box-shadow: 0 18px 42px rgba(37, 99, 235, 0.24);
}
.js-multistep-validation-next:hover,
.js-multistep-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(37, 99, 235, 0.34);
}
.js-multistep-validation-back[hidden],
.js-multistep-validation-next[hidden],
.js-multistep-validation-submit[hidden] {
display: none;
}
.js-multistep-validation-result {
display: none;
padding: 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(16, 185, 129, 0.22);
color: #065f46;
font-size: 14px;
line-height: 1.55;
font-weight: 850;
}
.js-multistep-validation-result.is-visible {
display: block;
}
@media (max-width: 960px) {
.js-multistep-validation-shell {
grid-template-columns: 1fr;
}
.js-multistep-validation-sidebar {
min-height: 360px;
}
}
@media (max-width: 680px) {
.js-multistep-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-multistep-validation-sidebar,
.js-multistep-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-multistep-validation-grid,
.js-multistep-validation-choice-grid {
grid-template-columns: 1fr;
}
.js-multistep-validation-grid .is-full {
grid-column: auto;
}
.js-multistep-validation-head h3 {
font-size: 30px;
}
.js-multistep-validation-actions {
flex-direction: column;
}
.js-multistep-validation-back,
.js-multistep-validation-next,
.js-multistep-validation-submit {
width: 100%;
}
}
This multi-step form validation example is useful when a form contains several groups of information. You can adapt it for quote request forms, service onboarding, application forms, booking flows, project questionnaires, customer intake forms, and lead qualification pages.
9. Phone Number Validation Form
Phone number validation helps forms collect cleaner contact details before a user submits a callback request, booking form, checkout form, quote form, account form, or lead form. Phone fields are often typed in different formats, so a helpful validation pattern should guide the user instead of only blocking submission.
This example uses a modern callback request layout with a country selector, phone number formatting, digit-only cleanup, country-specific hints, inline validation messages, and a success state. It is useful for service websites, appointment forms, sales calls, booking pages, and contact forms that require a phone number.
- Phone number validation with JavaScript
- Includes country selector and format hints
- Automatically formats digits while typing
- Validates minimum digit length based on selected country
- Good for callback forms, booking forms, quote forms, and contact pages
Collect better callback numbers
Use clear formatting hints and country-based validation to help users enter phone numbers correctly before sending a request.
Request a callback
Choose a country and type a phone number. The demo formats digits and validates the expected length.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-phone-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-phone-validation-form]");
const result = demo.querySelector("[data-js-phone-result]");
const hint = demo.querySelector("[data-phone-hint]");
const fields = {
name: form.querySelector("[name='name']"),
country: form.querySelector("[name='country']"),
phone: form.querySelector("[name='phone']"),
message: form.querySelector("[name='message']")
};
const countryRules = {
us: {
prefix: "+1",
min: 10,
max: 10,
hint: "US numbers usually need 10 digits.",
placeholder: "555 123 4567"
},
uk: {
prefix: "+44",
min: 10,
max: 11,
hint: "UK numbers usually need 10 or 11 digits.",
placeholder: "7700 900123"
},
ee: {
prefix: "+372",
min: 7,
max: 8,
hint: "Estonian numbers usually need 7 or 8 digits.",
placeholder: "5123 4567"
},
de: {
prefix: "+49",
min: 10,
max: 12,
hint: "German numbers often need 10 to 12 digits.",
placeholder: "1512 3456789"
}
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-phone-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-phone-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function formatPhone(value) {
const digits = value.replace(/\D/g, "").slice(0, 12);
return digits.replace(/(\d{3})(?=\d)/g, "$1 ").trim();
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validatePhone() {
const rule = countryRules[fields.country.value];
const digits = fields.phone.value.replace(/\D/g, "");
if (digits.length < rule.min || digits.length > rule.max) {
setField("phone", false, "Enter a valid phone number for " + rule.prefix + ". Expected " + rule.min + (rule.min !== rule.max ? "-" + rule.max : "") + " digits.");
return false;
}
setField("phone", true, "Phone number format looks valid for " + rule.prefix + ".");
return true;
}
function validateMessage() {
if (fields.message.value.trim().length < 10) {
setField("message", false, "Write at least 10 characters.");
return false;
}
setField("message", true, "Message looks good.");
return true;
}
function updateCountryHint() {
const rule = countryRules[fields.country.value];
hint.textContent = rule.hint;
fields.phone.placeholder = rule.placeholder;
fields.phone.value = "";
demo.querySelector("[data-phone-message='phone']").textContent = "";
demo.querySelector("[data-phone-field='phone']").classList.remove("is-valid", "is-error");
result.classList.remove("is-visible");
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.phone.addEventListener("input", function () {
fields.phone.value = formatPhone(fields.phone.value);
validatePhone();
result.classList.remove("is-visible");
});
fields.message.addEventListener("input", function () {
validateMessage();
result.classList.remove("is-visible");
});
fields.country.addEventListener("change", function () {
updateCountryHint();
});
form.addEventListener("submit", function (event) {
event.preventDefault();
const isValid = validateName() && validatePhone() && validateMessage();
if (isValid) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateCountryHint();
});
})();
HTML
<div class="js-phone-validation-preview" data-js-phone-validation-demo>
<div class="js-phone-validation-hero">
<div>
<div class="js-phone-validation-kicker">Phone Validation</div>
<h3>Collect better callback numbers</h3>
<p>Use clear formatting hints and country-based validation to help users enter phone numbers correctly before sending a request.</p>
</div>
<div class="js-phone-validation-bubbles">
<div class="js-phone-validation-bubble">
<strong>Auto format</strong>
<span>Spaces are added while typing.</span>
</div>
<div class="js-phone-validation-bubble">
<strong>Country hint</strong>
<span>Rules update by selected country.</span>
</div>
<div class="js-phone-validation-bubble">
<strong>Clean digits</strong>
<span>Letters are removed automatically.</span>
</div>
</div>
</div>
<div class="js-phone-validation-card">
<h3>Request a callback</h3>
<p class="js-phone-validation-card-text">Choose a country and type a phone number. The demo formats digits and validates the expected length.</p>
<form class="js-phone-validation-form" novalidate data-js-phone-validation-form>
<div class="js-phone-validation-field" data-phone-field="name">
<label for="phone-validation-name">Name</label>
<input id="phone-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-phone-validation-message" data-phone-message="name"></div>
</div>
<div class="js-phone-validation-field" data-phone-field="phone">
<label for="phone-validation-number">Phone number</label>
<div class="js-phone-validation-number-row">
<select name="country" aria-label="Country code">
<option value="us">US +1</option>
<option value="uk">UK +44</option>
<option value="ee">EE +372</option>
<option value="de">DE +49</option>
</select>
<input id="phone-validation-number" name="phone" type="tel" inputmode="tel" placeholder="555 123 4567">
</div>
<div class="js-phone-validation-hint" data-phone-hint>US numbers usually need 10 digits.</div>
<div class="js-phone-validation-message" data-phone-message="phone"></div>
</div>
<div class="js-phone-validation-field" data-phone-field="message">
<label for="phone-validation-message">Short note</label>
<textarea id="phone-validation-message" name="message" placeholder="What should we call you about?"></textarea>
<div class="js-phone-validation-message" data-phone-message="message"></div>
</div>
<button class="js-phone-validation-submit" type="submit">Validate Callback Request</button>
<div class="js-phone-validation-result" data-js-phone-result>
Phone validation passed. This callback request can now be connected to your CRM, email system, or booking backend.
</div>
</form>
</div>
</div>
CSS
.js-phone-validation-preview {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) 380px;
gap: 26px;
align-items: stretch;
min-height: 620px;
padding: 34px;
border-radius: 28px;
overflow: hidden;
background:
radial-gradient(circle at 12% 16%, rgba(20, 184, 166, 0.16), transparent 34%),
radial-gradient(circle at 88% 20%, rgba(14, 165, 233, 0.15), transparent 34%),
linear-gradient(135deg, #ecfeff 0%, #ffffff 52%, #f0fdfa 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-phone-validation-hero {
position: relative;
overflow: hidden;
padding: 36px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 20%, rgba(255, 255, 255, 0.18), transparent 34%),
linear-gradient(135deg, #042f2e 0%, #0f766e 48%, #0369a1 100%);
box-shadow: 0 26px 80px rgba(15, 118, 110, 0.25);
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 30px;
}
.js-phone-validation-hero::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.11);
}
.js-phone-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #ccfbf1;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-phone-validation-hero h3 {
position: relative;
z-index: 2;
max-width: 600px;
margin: 0 0 16px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(38px, 5vw, 68px);
line-height: 0.99;
font-weight: 950;
letter-spacing: -0.08em;
}
.js-phone-validation-hero p {
position: relative;
z-index: 2;
max-width: 560px;
margin: 0;
color: #ccfbf1;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-phone-validation-bubbles {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
.js-phone-validation-bubble {
padding: 16px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-phone-validation-bubble strong {
display: block;
margin-bottom: 6px;
color: #ffffff;
font-size: 15px;
font-weight: 950;
line-height: 1.2;
}
.js-phone-validation-bubble span {
display: block;
color: #ccfbf1;
font-size: 12px;
line-height: 1.4;
font-weight: 700;
}
.js-phone-validation-card {
padding: 30px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
display: flex;
flex-direction: column;
justify-content: center;
}
.js-phone-validation-card h3 {
margin: 0 0 10px;
color: #0f172a;
font-size: 32px;
line-height: 1.1;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-phone-validation-card-text {
margin: 0 0 22px;
color: #64748b;
font-size: 15px;
line-height: 1.65;
font-weight: 650;
}
.js-phone-validation-form {
display: grid;
gap: 16px;
}
.js-phone-validation-field {
display: grid;
gap: 8px;
}
.js-phone-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-phone-validation-field input,
.js-phone-validation-field select,
.js-phone-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 52px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 17px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-phone-validation-field textarea {
min-height: 100px;
resize: vertical;
}
.js-phone-validation-field input:focus,
.js-phone-validation-field select:focus,
.js-phone-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(20, 184, 166, 0.62);
box-shadow: 0 0 0 4px rgba(20, 184, 166, 0.12);
}
.js-phone-validation-field.is-error input,
.js-phone-validation-field.is-error select,
.js-phone-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-phone-validation-field.is-valid input,
.js-phone-validation-field.is-valid select,
.js-phone-validation-field.is-valid textarea {
border-color: rgba(20, 184, 166, 0.58);
background: #f0fdfa;
}
.js-phone-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-phone-validation-field.is-valid .js-phone-validation-message {
color: #0f766e;
}
.js-phone-validation-number-row {
display: grid;
grid-template-columns: 116px 1fr;
gap: 10px;
}
.js-phone-validation-hint {
padding: 12px 14px;
border-radius: 16px;
background: #f0fdfa;
color: #0f766e;
font-size: 13px;
line-height: 1.45;
font-weight: 800;
}
.js-phone-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #14b8a6, #0284c7);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(20, 184, 166, 0.25);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-phone-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(20, 184, 166, 0.34);
}
.js-phone-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(16, 185, 129, 0.22);
color: #065f46;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-phone-validation-result.is-visible {
display: block;
}
@media (max-width: 960px) {
.js-phone-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-phone-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-phone-validation-hero,
.js-phone-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-phone-validation-bubbles {
grid-template-columns: 1fr;
}
.js-phone-validation-number-row {
grid-template-columns: 1fr;
}
.js-phone-validation-hero h3,
.js-phone-validation-card h3 {
font-size: 32px;
}
}
This phone number validation example can be adapted for callback forms, booking forms, appointment requests, lead generation pages, checkout contact details, and quote request forms. In production, phone validation should still be checked again on the server before storing or sending the data.
10. File Upload Validation
File upload validation is important when a form allows users to attach resumes, project files, images, documents, invoices, screenshots, or support attachments. A good upload field should clearly explain allowed file types, maximum file size, and whether the file is required.
This example uses a drag-and-drop style upload card with file type validation, file size validation, selected file preview, remove file button, and a success message. It is useful for job application forms, support forms, quote request forms, document upload pages, profile forms, and client portals.
- JavaScript file upload validation
- Checks allowed file types and maximum file size
- Shows selected file name and file size
- Includes remove file button and success state
- Good for CV uploads, support attachments, document forms, and quote request forms
Submit your file
Try selecting a file. The demo accepts PDF, DOC, DOCX, PNG, and JPG files up to 5 MB.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-file-upload-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-file-upload-form]");
const zone = demo.querySelector("[data-js-upload-zone]");
const fileInput = form.querySelector("[name='attachment']");
const filePreview = demo.querySelector("[data-js-upload-file]");
const fileName = demo.querySelector("[data-js-upload-name]");
const fileSize = demo.querySelector("[data-js-upload-size]");
const removeButton = demo.querySelector("[data-js-upload-remove]");
const result = demo.querySelector("[data-js-upload-result]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']")
};
const allowedExtensions = ["pdf", "doc", "docx", "png", "jpg", "jpeg"];
const maxFileSize = 5 * 1024 * 1024;
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-upload-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-upload-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function formatFileSize(bytes) {
if (bytes < 1024 * 1024) {
return Math.round(bytes / 1024) + " KB";
}
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
}
function getExtension(name) {
return name.split(".").pop().toLowerCase();
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateFile() {
const file = fileInput.files[0];
if (!file) {
setField("file", false, "Please choose one file.");
filePreview.classList.remove("is-visible");
return false;
}
const extension = getExtension(file.name);
if (!allowedExtensions.includes(extension)) {
setField("file", false, "File type not allowed. Use PDF, DOC, DOCX, PNG, or JPG.");
filePreview.classList.remove("is-visible");
return false;
}
if (file.size > maxFileSize) {
setField("file", false, "File is too large. Maximum size is 5 MB.");
filePreview.classList.remove("is-visible");
return false;
}
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
filePreview.classList.add("is-visible");
setField("file", true, "File looks valid.");
return true;
}
function clearResult() {
result.classList.remove("is-visible");
}
fields.name.addEventListener("input", function () {
validateName();
clearResult();
});
fields.email.addEventListener("input", function () {
validateEmail();
clearResult();
});
fileInput.addEventListener("change", function () {
validateFile();
clearResult();
});
removeButton.addEventListener("click", function () {
fileInput.value = "";
filePreview.classList.remove("is-visible");
setField("file", false, "Please choose one file.");
clearResult();
});
zone.addEventListener("dragover", function (event) {
event.preventDefault();
zone.classList.add("is-dragover");
});
zone.addEventListener("dragleave", function () {
zone.classList.remove("is-dragover");
});
zone.addEventListener("drop", function (event) {
event.preventDefault();
zone.classList.remove("is-dragover");
if (event.dataTransfer.files.length) {
fileInput.files = event.dataTransfer.files;
validateFile();
clearResult();
}
});
form.addEventListener("submit", function (event) {
event.preventDefault();
const isValid = validateName() && validateEmail() && validateFile();
if (isValid) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
});
})();
HTML
<div class="js-file-upload-validation-preview" data-js-file-upload-validation-demo>
<div class="js-file-upload-validation-shell">
<aside class="js-file-upload-validation-info">
<div>
<div class="js-file-upload-validation-badge">Upload Validation</div>
<h3>Validate files before upload</h3>
<p>Check file type, file size, and required attachments before a form is submitted to your backend.</p>
</div>
<div class="js-file-upload-validation-rules">
<div class="js-file-upload-validation-rule">
<div class="js-file-upload-validation-rule-icon">PDF</div>
<div>
<strong>Allowed formats</strong>
<span>PDF, DOC, DOCX, PNG, JPG</span>
</div>
</div>
<div class="js-file-upload-validation-rule">
<div class="js-file-upload-validation-rule-icon">5M</div>
<div>
<strong>Maximum size</strong>
<span>Files must be under 5 MB</span>
</div>
</div>
<div class="js-file-upload-validation-rule">
<div class="js-file-upload-validation-rule-icon">✓</div>
<div>
<strong>Required file</strong>
<span>The form needs one attachment</span>
</div>
</div>
</div>
</aside>
<div class="js-file-upload-validation-card">
<h3>Submit your file</h3>
<p class="js-file-upload-validation-card-text">Try selecting a file. The demo accepts PDF, DOC, DOCX, PNG, and JPG files up to 5 MB.</p>
<form class="js-file-upload-validation-form" novalidate data-js-file-upload-form>
<div class="js-file-upload-validation-field" data-upload-field="name">
<label for="file-upload-name">Full name</label>
<input id="file-upload-name" name="name" type="text" placeholder="Your name">
<div class="js-file-upload-validation-message" data-upload-message="name"></div>
</div>
<div class="js-file-upload-validation-field" data-upload-field="email">
<label for="file-upload-email">Email address</label>
<input id="file-upload-email" name="email" type="email" placeholder="you@example.com">
<div class="js-file-upload-validation-message" data-upload-message="email"></div>
</div>
<div class="js-file-upload-validation-field" data-upload-field="file">
<label for="file-upload-input">Attachment</label>
<div class="js-file-upload-validation-zone" data-js-upload-zone>
<input id="file-upload-input" name="attachment" type="file" accept=".pdf,.doc,.docx,.png,.jpg,.jpeg">
<div>
<div class="js-file-upload-validation-upload-icon">↑</div>
<strong>Choose or drop a file</strong>
<span>Accepted: PDF, DOC, DOCX, PNG, JPG. Maximum size: 5 MB.</span>
</div>
</div>
<div class="js-file-upload-validation-message" data-upload-message="file"></div>
<div class="js-file-upload-validation-file" data-js-upload-file>
<div class="js-file-upload-validation-file-info">
<strong data-js-upload-name>Selected file</strong>
<span data-js-upload-size>0 KB</span>
</div>
<button class="js-file-upload-validation-remove" type="button" data-js-upload-remove>Remove</button>
</div>
</div>
<button class="js-file-upload-validation-submit" type="submit">Validate Upload</button>
<div class="js-file-upload-validation-result" data-js-upload-result>
File upload validation passed. This demo is ready to connect to a real upload handler.
</div>
</form>
</div>
</div>
</div>
CSS
.js-file-upload-validation-preview {
display: grid;
place-items: center;
min-height: 690px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 16% 14%, rgba(168, 85, 247, 0.16), transparent 34%),
radial-gradient(circle at 84% 18%, rgba(59, 130, 246, 0.15), transparent 34%),
linear-gradient(135deg, #faf5ff 0%, #ffffff 52%, #eff6ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-file-upload-validation-shell {
width: min(100%, 1040px);
display: grid;
grid-template-columns: minmax(0, 0.95fr) minmax(360px, 1.05fr);
gap: 26px;
align-items: stretch;
}
.js-file-upload-validation-info {
position: relative;
overflow: hidden;
padding: 34px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.18), transparent 34%),
linear-gradient(135deg, #2e1065 0%, #6d28d9 48%, #2563eb 100%);
box-shadow: 0 26px 80px rgba(109, 40, 217, 0.25);
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 28px;
}
.js-file-upload-validation-info::before {
content: "";
position: absolute;
left: -110px;
bottom: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-file-upload-validation-badge {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #ede9fe;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-file-upload-validation-info h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(38px, 5vw, 64px);
line-height: 1;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-file-upload-validation-info p {
position: relative;
z-index: 2;
max-width: 480px;
margin: 0;
color: #ede9fe;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-file-upload-validation-rules {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
}
.js-file-upload-validation-rule {
display: grid;
grid-template-columns: 42px 1fr;
gap: 12px;
align-items: center;
padding: 14px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-file-upload-validation-rule-icon {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 15px;
background: rgba(255, 255, 255, 0.15);
color: #ffffff;
font-size: 20px;
}
.js-file-upload-validation-rule strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 14px;
line-height: 1.2;
font-weight: 950;
}
.js-file-upload-validation-rule span {
display: block;
color: #ede9fe;
font-size: 12px;
line-height: 1.4;
font-weight: 700;
}
.js-file-upload-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-file-upload-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 34px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-file-upload-validation-card-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-file-upload-validation-form {
display: grid;
gap: 16px;
}
.js-file-upload-validation-field {
display: grid;
gap: 8px;
}
.js-file-upload-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-file-upload-validation-field input[type="text"],
.js-file-upload-validation-field input[type="email"] {
width: 100%;
box-sizing: border-box;
min-height: 52px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 17px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-file-upload-validation-field input[type="text"]:focus,
.js-file-upload-validation-field input[type="email"]:focus {
background: #ffffff;
border-color: rgba(109, 40, 217, 0.62);
box-shadow: 0 0 0 4px rgba(109, 40, 217, 0.11);
}
.js-file-upload-validation-field.is-error input[type="text"],
.js-file-upload-validation-field.is-error input[type="email"] {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-file-upload-validation-field.is-valid input[type="text"],
.js-file-upload-validation-field.is-valid input[type="email"] {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-file-upload-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-file-upload-validation-field.is-valid .js-file-upload-validation-message {
color: #15803d;
}
.js-file-upload-validation-zone {
position: relative;
display: grid;
place-items: center;
min-height: 210px;
padding: 24px;
border: 2px dashed rgba(109, 40, 217, 0.30);
border-radius: 26px;
background:
radial-gradient(circle at 50% 0%, rgba(109, 40, 217, 0.08), transparent 48%),
#faf5ff;
text-align: center;
cursor: pointer;
transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}
.js-file-upload-validation-zone:hover,
.js-file-upload-validation-zone.is-dragover {
transform: translateY(-2px);
border-color: rgba(109, 40, 217, 0.62);
background: #f5f3ff;
}
.js-file-upload-validation-zone input {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
}
.js-file-upload-validation-upload-icon {
display: grid;
place-items: center;
width: 72px;
height: 72px;
margin: 0 auto 14px;
border-radius: 24px;
background: linear-gradient(135deg, #7c3aed, #2563eb);
color: #ffffff;
font-size: 34px;
box-shadow: 0 18px 40px rgba(109, 40, 217, 0.24);
}
.js-file-upload-validation-zone strong {
display: block;
margin-bottom: 8px;
color: #111827;
font-size: 18px;
line-height: 1.25;
font-weight: 950;
}
.js-file-upload-validation-zone span {
display: block;
color: #64748b;
font-size: 13px;
line-height: 1.45;
font-weight: 700;
}
.js-file-upload-validation-file {
display: none;
align-items: center;
justify-content: space-between;
gap: 14px;
padding: 14px;
border-radius: 18px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-file-upload-validation-file.is-visible {
display: flex;
}
.js-file-upload-validation-file-info {
min-width: 0;
}
.js-file-upload-validation-file-info strong {
display: block;
overflow: hidden;
color: #111827;
font-size: 14px;
font-weight: 950;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
}
.js-file-upload-validation-file-info span {
display: block;
margin-top: 4px;
color: #64748b;
font-size: 12px;
font-weight: 750;
}
.js-file-upload-validation-remove {
flex: 0 0 auto;
min-height: 38px;
padding: 10px 13px;
border: 0;
border-radius: 999px;
background: #fee2e2;
color: #b91c1c;
font-size: 12px;
line-height: 1;
font-weight: 900;
cursor: pointer;
}
.js-file-upload-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #7c3aed, #2563eb);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(109, 40, 217, 0.26);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-file-upload-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(109, 40, 217, 0.35);
}
.js-file-upload-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-file-upload-validation-result.is-visible {
display: block;
}
@media (max-width: 960px) {
.js-file-upload-validation-shell {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-file-upload-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-file-upload-validation-info,
.js-file-upload-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-file-upload-validation-info h3,
.js-file-upload-validation-card h3 {
font-size: 32px;
}
.js-file-upload-validation-file {
align-items: flex-start;
flex-direction: column;
}
}
This file upload validation example is useful for job application forms, support tickets, quote request forms, client portals, invoice upload pages, profile forms, and document submission flows. For real websites, always repeat file validation on the server before accepting or storing uploads.
11. Booking Form Validation
Booking form validation helps users choose a valid appointment date, time, guest count, and contact details before submitting a reservation or appointment request. This pattern is useful for hotels, restaurants, clinics, salons, consultants, event pages, and service businesses.
This example validates name, email, booking date, time slot, guest count, and a short note. It blocks past dates, requires a time slot, checks guest limits, and shows inline error messages with a polished booking-style layout.
- Validates name, email, date, time, guests, and notes
- Blocks past dates with JavaScript
- Uses a booking calendar-style layout
- Shows clear inline validation messages
- Good for appointment, reservation, and booking forms
Book an appointment
Select a future date, choose a time slot, and add your contact details.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-booking-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-booking-validation-form]");
const result = demo.querySelector("[data-js-booking-result]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
date: form.querySelector("[name='date']"),
time: form.querySelector("[name='time']"),
guests: form.querySelector("[name='guests']"),
notes: form.querySelector("[name='notes']")
};
const today = new Date();
const todayString = today.toISOString().split("T")[0];
fields.date.min = todayString;
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-booking-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-booking-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your full name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateDate() {
if (!fields.date.value) {
setField("date", false, "Choose a booking date.");
return false;
}
if (fields.date.value < todayString) {
setField("date", false, "Past dates are not available.");
return false;
}
setField("date", true, "Date is available.");
return true;
}
function validateTime() {
if (!fields.time.value) {
setField("time", false, "Choose a time slot.");
return false;
}
setField("time", true, "Time slot selected.");
return true;
}
function validateGuests() {
const guests = Number(fields.guests.value);
if (!guests || guests < 1 || guests > 8) {
setField("guests", false, "Enter a number between 1 and 8.");
return false;
}
setField("guests", true, "Guest count looks good.");
return true;
}
function validateNotes() {
if (fields.notes.value.trim().length < 10) {
setField("notes", false, "Write at least 10 characters.");
return false;
}
setField("notes", true, "Note looks good.");
return true;
}
function validateBooking() {
return validateName() &&
validateEmail() &&
validateDate() &&
validateTime() &&
validateGuests() &&
validateNotes();
}
Object.keys(fields).forEach(function (key) {
const eventType = fields[key].tagName === "SELECT" || fields[key].type === "date" ? "change" : "input";
fields[key].addEventListener(eventType, function () {
result.classList.remove("is-visible");
if (key === "name") validateName();
if (key === "email") validateEmail();
if (key === "date") validateDate();
if (key === "time") validateTime();
if (key === "guests") validateGuests();
if (key === "notes") validateNotes();
});
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateBooking()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
});
})();
HTML
<div class="js-booking-validation-preview" data-js-booking-validation-demo>
<aside class="js-booking-validation-calendar">
<div class="js-booking-validation-kicker">Booking Validation</div>
<h3>Reserve a time that actually works</h3>
<p>Validate appointment details before the booking request is sent to your calendar, email, CRM, or reservation system.</p>
<div class="js-booking-validation-month">
<div class="js-booking-validation-month-head">
<span>Available Days</span>
<span>This Month</span>
</div>
<div class="js-booking-validation-days">
<span>M</span><span>T</span><span>W</span><span>T</span><span>F</span><span>S</span><span>S</span>
<span></span><span></span><span class="is-hot">3</span><span>4</span><span>5</span><span>6</span><span>7</span>
<span>8</span><span class="is-hot">9</span><span>10</span><span>11</span><span class="is-hot">12</span><span>13</span><span>14</span>
<span>15</span><span>16</span><span class="is-hot">17</span><span>18</span><span>19</span><span>20</span><span>21</span>
</div>
</div>
<div class="js-booking-validation-info-list">
<div class="js-booking-validation-info-item">
<div class="js-booking-validation-info-icon">⏱</div>
<div>
<strong>Fast confirmation</strong>
<span>Validate fields before sending the booking.</span>
</div>
</div>
<div class="js-booking-validation-info-item">
<div class="js-booking-validation-info-icon">✓</div>
<div>
<strong>No past dates</strong>
<span>JavaScript blocks invalid booking dates.</span>
</div>
</div>
</div>
</aside>
<div class="js-booking-validation-card">
<h3>Book an appointment</h3>
<p class="js-booking-validation-card-text">Select a future date, choose a time slot, and add your contact details.</p>
<form class="js-booking-validation-form" novalidate data-js-booking-validation-form>
<div class="js-booking-validation-grid">
<div class="js-booking-validation-field" data-booking-field="name">
<label for="booking-validation-name">Full name</label>
<input id="booking-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-booking-validation-message" data-booking-message="name"></div>
</div>
<div class="js-booking-validation-field" data-booking-field="email">
<label for="booking-validation-email">Email address</label>
<input id="booking-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-booking-validation-message" data-booking-message="email"></div>
</div>
<div class="js-booking-validation-field" data-booking-field="date">
<label for="booking-validation-date">Booking date</label>
<input id="booking-validation-date" name="date" type="date">
<div class="js-booking-validation-message" data-booking-message="date"></div>
</div>
<div class="js-booking-validation-field" data-booking-field="time">
<label for="booking-validation-time">Time slot</label>
<select id="booking-validation-time" name="time">
<option value="">Choose a time</option>
<option value="09:00">09:00</option>
<option value="11:30">11:30</option>
<option value="14:00">14:00</option>
<option value="16:30">16:30</option>
</select>
<div class="js-booking-validation-message" data-booking-message="time"></div>
</div>
<div class="js-booking-validation-field" data-booking-field="guests">
<label for="booking-validation-guests">Guests / people</label>
<input id="booking-validation-guests" name="guests" type="number" min="1" max="8" placeholder="1-8">
<div class="js-booking-validation-message" data-booking-message="guests"></div>
</div>
<div class="js-booking-validation-field is-full" data-booking-field="notes">
<label for="booking-validation-notes">Short note</label>
<textarea id="booking-validation-notes" name="notes" placeholder="Add booking details or special requests..."></textarea>
<div class="js-booking-validation-message" data-booking-message="notes"></div>
</div>
</div>
<button class="js-booking-validation-submit" type="submit">Validate Booking</button>
<div class="js-booking-validation-result" data-js-booking-result>
Booking validation passed. This request can now be sent to your reservation system, CRM, or email handler.
</div>
</form>
</div>
</div>
CSS
.js-booking-validation-preview {
display: grid;
grid-template-columns: 360px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
min-height: 650px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 14% 14%, rgba(249, 115, 22, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.14), transparent 34%),
linear-gradient(135deg, #fff7ed 0%, #ffffff 52%, #eff6ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-booking-validation-calendar {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #7c2d12 0%, #ea580c 50%, #0369a1 100%);
color: #ffffff;
box-shadow: 0 26px 80px rgba(234, 88, 12, 0.24);
}
.js-booking-validation-calendar::before {
content: "";
position: absolute;
right: -110px;
top: -110px;
width: 260px;
height: 260px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-booking-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #ffedd5;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-booking-validation-calendar h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 42px;
line-height: 1.04;
font-weight: 950;
letter-spacing: -0.065em;
}
.js-booking-validation-calendar p {
position: relative;
z-index: 2;
margin: 0 0 28px;
color: #ffedd5;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-booking-validation-month {
position: relative;
z-index: 2;
padding: 18px;
border-radius: 24px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-booking-validation-month-head {
display: flex;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
color: #ffffff;
font-size: 14px;
font-weight: 950;
}
.js-booking-validation-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 7px;
}
.js-booking-validation-days span {
display: grid;
place-items: center;
height: 34px;
border-radius: 11px;
color: #ffedd5;
font-size: 12px;
font-weight: 800;
}
.js-booking-validation-days .is-hot {
background: #ffffff;
color: #ea580c;
}
.js-booking-validation-info-list {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
margin-top: 24px;
}
.js-booking-validation-info-item {
display: grid;
grid-template-columns: 40px 1fr;
gap: 12px;
align-items: center;
padding: 13px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.js-booking-validation-info-icon {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.16);
color: #ffffff;
font-size: 18px;
}
.js-booking-validation-info-item strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 13px;
font-weight: 950;
line-height: 1.2;
}
.js-booking-validation-info-item span {
display: block;
color: #ffedd5;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
.js-booking-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-booking-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 34px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-booking-validation-card-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-booking-validation-form {
display: grid;
gap: 16px;
}
.js-booking-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-booking-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-booking-validation-field {
display: grid;
gap: 7px;
}
.js-booking-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-booking-validation-field input,
.js-booking-validation-field select,
.js-booking-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 52px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 17px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-booking-validation-field textarea {
min-height: 105px;
resize: vertical;
}
.js-booking-validation-field input:focus,
.js-booking-validation-field select:focus,
.js-booking-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(234, 88, 12, 0.62);
box-shadow: 0 0 0 4px rgba(234, 88, 12, 0.11);
}
.js-booking-validation-field.is-error input,
.js-booking-validation-field.is-error select,
.js-booking-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-booking-validation-field.is-valid input,
.js-booking-validation-field.is-valid select,
.js-booking-validation-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-booking-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-booking-validation-field.is-valid .js-booking-validation-message {
color: #15803d;
}
.js-booking-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #f97316, #0284c7);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(234, 88, 12, 0.25);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-booking-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(234, 88, 12, 0.34);
}
.js-booking-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-booking-validation-result.is-visible {
display: block;
}
@media (max-width: 920px) {
.js-booking-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-booking-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-booking-validation-calendar,
.js-booking-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-booking-validation-grid {
grid-template-columns: 1fr;
}
.js-booking-validation-grid .is-full {
grid-column: auto;
}
.js-booking-validation-calendar h3,
.js-booking-validation-card h3 {
font-size: 30px;
}
}
This booking form validation example is a useful pattern for appointment forms, event reservations, table booking forms, service scheduling, hotel inquiries, consultation requests, and any form where users must choose a valid future date and time.
12. Product Review Form Validation
Product review form validation helps ecommerce websites, marketplaces, SaaS review pages, and testimonial sections collect more useful feedback. A review form should make sure the user chooses a rating, writes enough detail, and fills in the required identity fields before submitting.
This example validates star rating, review title, review text, name, and email. It uses an ecommerce-style product card, interactive star buttons, a live review length counter, and clear validation states.
- Validates star rating, review title, review text, name, and email
- Includes clickable star rating buttons
- Uses review text character counter
- Shows inline validation messages and success state
- Good for ecommerce product reviews, testimonials, SaaS feedback, and marketplace ratings
Write a review
Choose a star rating and write a useful review before submitting.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-review-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-review-validation-form]");
const stars = Array.from(demo.querySelectorAll("[data-rating]"));
const ratingMessage = demo.querySelector("[data-review-message='rating']");
const reviewCounter = demo.querySelector("[data-js-review-count]");
const result = demo.querySelector("[data-js-review-result]");
const fields = {
title: form.querySelector("[name='title']"),
review: form.querySelector("[name='review']"),
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']")
};
let selectedRating = 0;
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-review-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-review-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function validateRating() {
if (selectedRating < 1) {
ratingMessage.textContent = "Choose a star rating.";
ratingMessage.classList.remove("is-valid");
return false;
}
ratingMessage.textContent = selectedRating + " star rating selected.";
ratingMessage.classList.add("is-valid");
return true;
}
function validateTitle() {
if (fields.title.value.trim().length < 5) {
setField("title", false, "Write a title with at least 5 characters.");
return false;
}
setField("title", true, "Review title looks good.");
return true;
}
function validateReviewText() {
const length = fields.review.value.trim().length;
reviewCounter.textContent = String(length);
if (length < 50) {
setField("review", false, "Write at least 50 characters.");
return false;
}
setField("review", true, "Review text is detailed enough.");
return true;
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateAll() {
return validateRating() &&
validateTitle() &&
validateReviewText() &&
validateName() &&
validateEmail();
}
stars.forEach(function (star) {
star.addEventListener("click", function () {
selectedRating = Number(star.dataset.rating);
stars.forEach(function (item) {
item.classList.toggle("is-active", Number(item.dataset.rating) <= selectedRating);
});
validateRating();
result.classList.remove("is-visible");
});
});
fields.title.addEventListener("input", function () {
validateTitle();
result.classList.remove("is-visible");
});
fields.review.addEventListener("input", function () {
validateReviewText();
result.classList.remove("is-visible");
});
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
});
})();
HTML
<div class="js-review-validation-preview" data-js-review-validation-demo>
<aside class="js-review-validation-product">
<div>
<div class="js-review-validation-product-badge">Product Review</div>
</div>
<div class="js-review-validation-product-visual">
<div class="js-review-validation-box"></div>
</div>
<div>
<h3>Wireless Desk Lamp</h3>
<p>Demo product card for validating rating, review content, and customer details.</p>
<div class="js-review-validation-score">
<span>★★★★★</span>
<strong>4.8 average rating</strong>
</div>
</div>
</aside>
<div class="js-review-validation-card">
<h3>Write a review</h3>
<p class="js-review-validation-card-text">Choose a star rating and write a useful review before submitting.</p>
<form class="js-review-validation-form" novalidate data-js-review-validation-form>
<div class="js-review-validation-rating">
<div class="js-review-validation-rating-label">Your rating</div>
<div class="js-review-validation-stars" data-js-review-stars>
<button class="js-review-validation-star" type="button" data-rating="1">★</button>
<button class="js-review-validation-star" type="button" data-rating="2">★</button>
<button class="js-review-validation-star" type="button" data-rating="3">★</button>
<button class="js-review-validation-star" type="button" data-rating="4">★</button>
<button class="js-review-validation-star" type="button" data-rating="5">★</button>
</div>
<div class="js-review-validation-rating-message" data-review-message="rating"></div>
</div>
<div class="js-review-validation-grid">
<div class="js-review-validation-field is-full" data-review-field="title">
<label for="review-validation-title">Review title</label>
<input id="review-validation-title" name="title" type="text" placeholder="Example: Great product for my desk">
<div class="js-review-validation-message" data-review-message="title"></div>
</div>
<div class="js-review-validation-field is-full" data-review-field="review">
<label for="review-validation-review">Review text</label>
<textarea id="review-validation-review" name="review" placeholder="Write what you liked, what could be better, and who this product is for..."></textarea>
<div class="js-review-validation-counter"><span data-js-review-count>0</span>/50 minimum characters</div>
<div class="js-review-validation-message" data-review-message="review"></div>
</div>
<div class="js-review-validation-field" data-review-field="name">
<label for="review-validation-name">Your name</label>
<input id="review-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-review-validation-message" data-review-message="name"></div>
</div>
<div class="js-review-validation-field" data-review-field="email">
<label for="review-validation-email">Email address</label>
<input id="review-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-review-validation-message" data-review-message="email"></div>
</div>
</div>
<button class="js-review-validation-submit" type="submit">Validate Review</button>
<div class="js-review-validation-result" data-js-review-result>
Review validation passed. This review can now be saved, moderated, or sent to your ecommerce backend.
</div>
</form>
</div>
</div>
CSS
.js-review-validation-preview {
display: grid;
grid-template-columns: 340px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
min-height: 660px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 14% 14%, rgba(250, 204, 21, 0.18), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(244, 63, 94, 0.14), transparent 34%),
linear-gradient(135deg, #fefce8 0%, #ffffff 52%, #fff1f2 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-review-validation-product {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #713f12 0%, #ca8a04 48%, #be123c 100%);
box-shadow: 0 26px 80px rgba(202, 138, 4, 0.24);
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 28px;
}
.js-review-validation-product::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-review-validation-product-badge {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #fef3c7;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-review-validation-product-visual {
position: relative;
z-index: 2;
display: grid;
place-items: center;
min-height: 220px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
}
.js-review-validation-box {
width: 138px;
height: 138px;
border-radius: 34px;
background:
radial-gradient(circle at 30% 22%, rgba(255, 255, 255, 0.35), transparent 28%),
linear-gradient(135deg, #ffffff, #fde68a);
box-shadow: 0 30px 70px rgba(0, 0, 0, 0.20);
transform: rotate(-8deg);
}
.js-review-validation-product h3 {
position: relative;
z-index: 2;
margin: 0 0 10px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: 34px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-review-validation-product p {
position: relative;
z-index: 2;
margin: 0;
color: #fef3c7;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-review-validation-score {
position: relative;
z-index: 2;
display: flex;
align-items: center;
gap: 10px;
margin-top: 18px;
color: #ffffff;
font-size: 14px;
font-weight: 900;
}
.js-review-validation-score span {
color: #fef08a;
letter-spacing: 0.12em;
}
.js-review-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-review-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 34px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-review-validation-card-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-review-validation-form {
display: grid;
gap: 16px;
}
.js-review-validation-rating {
display: grid;
gap: 8px;
}
.js-review-validation-rating-label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-review-validation-stars {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.js-review-validation-star {
display: grid;
place-items: center;
width: 48px;
height: 48px;
border: 1px solid rgba(15, 23, 42, 0.12);
border-radius: 16px;
background: #f8fafc;
color: #94a3b8;
font-size: 23px;
line-height: 1;
cursor: pointer;
transition: transform 0.18s ease, background 0.18s ease, border-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
.js-review-validation-star:hover {
transform: translateY(-2px);
border-color: rgba(202, 138, 4, 0.42);
}
.js-review-validation-star.is-active {
background: #fef3c7;
border-color: rgba(202, 138, 4, 0.58);
color: #ca8a04;
box-shadow: 0 14px 30px rgba(202, 138, 4, 0.16);
}
.js-review-validation-rating-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-review-validation-rating-message.is-valid {
color: #15803d;
}
.js-review-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-review-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-review-validation-field {
display: grid;
gap: 7px;
}
.js-review-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-review-validation-field input,
.js-review-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 52px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 17px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-review-validation-field textarea {
min-height: 130px;
resize: vertical;
}
.js-review-validation-field input:focus,
.js-review-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(202, 138, 4, 0.62);
box-shadow: 0 0 0 4px rgba(202, 138, 4, 0.11);
}
.js-review-validation-field.is-error input,
.js-review-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-review-validation-field.is-valid input,
.js-review-validation-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-review-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-review-validation-field.is-valid .js-review-validation-message {
color: #15803d;
}
.js-review-validation-counter {
display: flex;
justify-content: flex-end;
margin-top: -4px;
color: #94a3b8;
font-size: 12px;
font-weight: 800;
}
.js-review-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #ca8a04, #be123c);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(190, 18, 60, 0.23);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-review-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(190, 18, 60, 0.32);
}
.js-review-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-review-validation-result.is-visible {
display: block;
}
@media (max-width: 920px) {
.js-review-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-review-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-review-validation-product,
.js-review-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-review-validation-grid {
grid-template-columns: 1fr;
}
.js-review-validation-grid .is-full {
grid-column: auto;
}
.js-review-validation-product h3,
.js-review-validation-card h3 {
font-size: 30px;
}
}
This product review form validation example is useful for ecommerce product pages, marketplace reviews, testimonial submission forms, software review pages, course feedback forms, and customer rating sections.
13. Payment Card Validation UI
Payment card validation UI helps users enter cardholder name, card number, expiry date, and CVV in a cleaner format before a payment form is submitted. This example is still front-end validation only, so real payment forms must always use secure payment providers and server-side validation.
- Validates cardholder name, card number, expiry date, and CVV
- Formats card number while typing
- Updates the visual card preview in real time
- Checks MM/YY expiry format and 3-4 digit CVV
- Good for checkout forms, donation forms, subscription forms, and payment screens
Payment details
Type card details to update the preview and validate each field before submit.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-card-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-card-validation-form]");
const result = demo.querySelector("[data-js-card-result]");
const fields = {
name: form.querySelector("[name='name']"),
number: form.querySelector("[name='number']"),
expiry: form.querySelector("[name='expiry']"),
cvv: form.querySelector("[name='cvv']")
};
const preview = {
name: demo.querySelector("[data-js-card-preview-name]"),
number: demo.querySelector("[data-js-card-preview-number]"),
expiry: demo.querySelector("[data-js-card-preview-expiry]"),
brand: demo.querySelector("[data-js-card-brand]")
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-card-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-card-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function getCardBrand(digits) {
if (/^4/.test(digits)) return "Visa";
if (/^5[1-5]/.test(digits)) return "Master";
if (/^3[47]/.test(digits)) return "Amex";
return "Card";
}
function validateName() {
const value = fields.name.value.trim();
preview.name.textContent = value || "Your Name";
if (value.length < 3) {
setField("name", false, "Enter the cardholder name.");
return false;
}
setField("name", true, "Cardholder name looks good.");
return true;
}
function validateNumber() {
const digits = fields.number.value.replace(/\D/g, "").slice(0, 19);
fields.number.value = digits.replace(/(.{4})/g, "$1 ").trim();
preview.number.textContent = fields.number.value || "•••• •••• •••• ••••";
preview.brand.textContent = getCardBrand(digits);
if (digits.length < 13 || digits.length > 19) {
setField("number", false, "Enter a valid card number.");
return false;
}
setField("number", true, "Card number format looks valid.");
return true;
}
function validateExpiry() {
let value = fields.expiry.value.replace(/\D/g, "").slice(0, 4);
if (value.length >= 3) {
value = value.slice(0, 2) + "/" + value.slice(2);
}
fields.expiry.value = value;
preview.expiry.textContent = value || "MM/YY";
const pattern = /^(0[1-9]|1[0-2])\/\d{2}$/;
if (!pattern.test(value)) {
setField("expiry", false, "Use MM/YY format.");
return false;
}
setField("expiry", true, "Expiry format is valid.");
return true;
}
function validateCvv() {
fields.cvv.value = fields.cvv.value.replace(/\D/g, "").slice(0, 4);
if (!/^\d{3,4}$/.test(fields.cvv.value)) {
setField("cvv", false, "Use 3 or 4 digits.");
return false;
}
setField("cvv", true, "CVV format is valid.");
return true;
}
function validateAll() {
return validateName() && validateNumber() && validateExpiry() && validateCvv();
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.number.addEventListener("input", function () {
validateNumber();
result.classList.remove("is-visible");
});
fields.expiry.addEventListener("input", function () {
validateExpiry();
result.classList.remove("is-visible");
});
fields.cvv.addEventListener("input", function () {
validateCvv();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
});
})();
HTML
<div class="js-card-validation-preview" data-js-card-validation-demo>
<aside class="js-card-validation-display">
<div>
<div class="js-card-validation-kicker">Card Validation</div>
<h3>Preview payment details while validating fields</h3>
</div>
<div class="js-card-validation-card-visual">
<div class="js-card-validation-card-top">
<div class="js-card-validation-chip"></div>
<div class="js-card-validation-brand" data-js-card-brand>Card</div>
</div>
<div class="js-card-validation-number" data-js-card-preview-number>•••• •••• •••• ••••</div>
<div class="js-card-validation-card-bottom">
<div class="js-card-validation-card-small">
<span>Cardholder</span>
<strong data-js-card-preview-name>Your Name</strong>
</div>
<div class="js-card-validation-card-small">
<span>Expires</span>
<strong data-js-card-preview-expiry>MM/YY</strong>
</div>
</div>
</div>
<div class="js-card-validation-security">
<div class="js-card-validation-security-item">
<div class="js-card-validation-security-icon">🔒</div>
<div>
<strong>Front-end check only</strong>
<span>Use a real payment provider in production.</span>
</div>
</div>
<div class="js-card-validation-security-item">
<div class="js-card-validation-security-icon">✓</div>
<div>
<strong>Clean formatting</strong>
<span>Spaces and date slash are added automatically.</span>
</div>
</div>
</div>
</aside>
<div class="js-card-validation-form-card">
<h3>Payment details</h3>
<p class="js-card-validation-text">Type card details to update the preview and validate each field before submit.</p>
<form class="js-card-validation-form" novalidate data-js-card-validation-form>
<div class="js-card-validation-grid">
<div class="js-card-validation-field is-full" data-card-field="name">
<label for="card-validation-name">Cardholder name</label>
<input id="card-validation-name" name="name" type="text" placeholder="Alex Morgan">
<div class="js-card-validation-message" data-card-message="name"></div>
</div>
<div class="js-card-validation-field is-full" data-card-field="number">
<label for="card-validation-number">Card number</label>
<input id="card-validation-number" name="number" type="text" inputmode="numeric" placeholder="4242 4242 4242 4242">
<div class="js-card-validation-message" data-card-message="number"></div>
</div>
<div class="js-card-validation-field" data-card-field="expiry">
<label for="card-validation-expiry">Expiry date</label>
<input id="card-validation-expiry" name="expiry" type="text" placeholder="MM/YY">
<div class="js-card-validation-message" data-card-message="expiry"></div>
</div>
<div class="js-card-validation-field" data-card-field="cvv">
<label for="card-validation-cvv">CVV</label>
<input id="card-validation-cvv" name="cvv" type="text" inputmode="numeric" placeholder="123">
<div class="js-card-validation-message" data-card-message="cvv"></div>
</div>
</div>
<button class="js-card-validation-submit" type="submit">Validate Payment Card</button>
<div class="js-card-validation-result" data-js-card-result>
Payment card validation passed. Use secure payment processing and server-side validation in production.
</div>
</form>
</div>
</div>
CSS
.js-card-validation-preview {
display: grid;
grid-template-columns: minmax(330px, 0.92fr) minmax(0, 1.08fr);
gap: 28px;
align-items: stretch;
min-height: 650px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 14% 16%, rgba(37, 99, 235, 0.18), transparent 34%),
radial-gradient(circle at 86% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
linear-gradient(135deg, #eff6ff 0%, #ffffff 52%, #faf5ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
/* Full CSS is included in the live preview block above. Copy the full style block from the live example for production use. */
This payment card validation UI is useful for checkout pages, billing forms, subscription forms, donation forms, paid booking forms, and payment screens. Always connect real payment forms to a secure payment provider and validate sensitive payment data outside the browser too.
14. Address Form Validation
Address form validation helps checkout, shipping, billing, booking, and delivery forms collect complete location details before submission. This example checks name, street address, city, country, postal code, and delivery notes.
- Validates shipping name, street address, city, country, postal code, and notes
- Changes postal code hints by country
- Shows a live delivery summary preview
- Uses inline error and success states
- Good for checkout forms, delivery forms, billing forms, and booking pages
Shipping address
Fill in a complete delivery address. The postal code rule changes based on the selected country.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-address-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-address-validation-form]");
const result = demo.querySelector("[data-js-address-result]");
const hint = demo.querySelector("[data-js-address-hint]");
const fields = {
name: form.querySelector("[name='name']"),
street: form.querySelector("[name='street']"),
city: form.querySelector("[name='city']"),
country: form.querySelector("[name='country']"),
postal: form.querySelector("[name='postal']"),
notes: form.querySelector("[name='notes']")
};
const preview = {
name: demo.querySelector("[data-js-address-preview-name]"),
street: demo.querySelector("[data-js-address-preview-street]"),
location: demo.querySelector("[data-js-address-preview-location]"),
postal: demo.querySelector("[data-js-address-preview-postal]")
};
const postalRules = {
"Estonia": {
pattern: /^\d{5}$/,
hint: "Estonian postal codes use 5 digits, for example 10111.",
placeholder: "10111"
},
"United States": {
pattern: /^\d{5}(-\d{4})?$/,
hint: "US ZIP codes use 5 digits or ZIP+4, for example 10001.",
placeholder: "10001"
},
"United Kingdom": {
pattern: /^[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2}$/i,
hint: "UK postcodes use formats like SW1A 1AA.",
placeholder: "SW1A 1AA"
},
"Germany": {
pattern: /^\d{5}$/,
hint: "German postal codes use 5 digits, for example 10115.",
placeholder: "10115"
}
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-address-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-address-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function updatePreview() {
preview.name.textContent = fields.name.value.trim() || "Recipient name";
preview.street.textContent = fields.street.value.trim() || "Street address";
const city = fields.city.value.trim() || "City";
const country = fields.country.value || "Country";
preview.location.textContent = city + ", " + country;
preview.postal.textContent = fields.postal.value.trim() || "Postal code";
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter the recipient name.");
return false;
}
setField("name", true, "Recipient name looks good.");
return true;
}
function validateStreet() {
if (fields.street.value.trim().length < 5) {
setField("street", false, "Enter a complete street address.");
return false;
}
setField("street", true, "Street address looks good.");
return true;
}
function validateCity() {
if (fields.city.value.trim().length < 2) {
setField("city", false, "Enter a city.");
return false;
}
setField("city", true, "City looks good.");
return true;
}
function validateCountry() {
if (!fields.country.value) {
setField("country", false, "Choose a country.");
return false;
}
setField("country", true, "Country selected.");
return true;
}
function validatePostal() {
const country = fields.country.value;
const value = fields.postal.value.trim();
if (!country || !postalRules[country]) {
setField("postal", false, "Choose a country first.");
return false;
}
if (!postalRules[country].pattern.test(value)) {
setField("postal", false, "Postal code does not match the selected country.");
return false;
}
setField("postal", true, "Postal code format looks valid.");
return true;
}
function validateNotes() {
if (fields.notes.value.trim().length < 5) {
setField("notes", false, "Add at least 5 characters for delivery notes.");
return false;
}
setField("notes", true, "Delivery notes look good.");
return true;
}
function updateCountryRule() {
const country = fields.country.value;
if (postalRules[country]) {
hint.textContent = postalRules[country].hint;
fields.postal.placeholder = postalRules[country].placeholder;
} else {
hint.textContent = "Choose a country to see the postal code rule.";
fields.postal.placeholder = "Choose country first";
}
}
function validateAll() {
return validateName() &&
validateStreet() &&
validateCity() &&
validateCountry() &&
validatePostal() &&
validateNotes();
}
Object.keys(fields).forEach(function (key) {
const eventType = fields[key].tagName === "SELECT" ? "change" : "input";
fields[key].addEventListener(eventType, function () {
updatePreview();
result.classList.remove("is-visible");
if (key === "country") {
updateCountryRule();
validateCountry();
if (fields.postal.value.trim()) {
validatePostal();
}
}
if (key === "name") validateName();
if (key === "street") validateStreet();
if (key === "city") validateCity();
if (key === "postal") validatePostal();
if (key === "notes") validateNotes();
});
});
form.addEventListener("submit", function (event) {
event.preventDefault();
updateCountryRule();
updatePreview();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateCountryRule();
updatePreview();
});
})();
HTML
<div class="js-address-validation-preview" data-js-address-validation-demo>
<div class="js-address-validation-card">
<h3>Shipping address</h3>
<p class="js-address-validation-text">Fill in a complete delivery address. The postal code rule changes based on the selected country.</p>
<form class="js-address-validation-form" novalidate data-js-address-validation-form>
<div class="js-address-validation-grid">
<div class="js-address-validation-field is-full" data-address-field="name">
<label for="address-validation-name">Recipient name</label>
<input id="address-validation-name" name="name" type="text" placeholder="Alex Morgan">
<div class="js-address-validation-message" data-address-message="name"></div>
</div>
<div class="js-address-validation-field is-full" data-address-field="street">
<label for="address-validation-street">Street address</label>
<input id="address-validation-street" name="street" type="text" placeholder="24 Market Street">
<div class="js-address-validation-message" data-address-message="street"></div>
</div>
<div class="js-address-validation-field" data-address-field="city">
<label for="address-validation-city">City</label>
<input id="address-validation-city" name="city" type="text" placeholder="Tallinn">
<div class="js-address-validation-message" data-address-message="city"></div>
</div>
<div class="js-address-validation-field" data-address-field="country">
<label for="address-validation-country">Country</label>
<select id="address-validation-country" name="country">
<option value="">Choose country</option>
<option value="Estonia">Estonia</option>
<option value="United States">United States</option>
<option value="United Kingdom">United Kingdom</option>
<option value="Germany">Germany</option>
</select>
<div class="js-address-validation-message" data-address-message="country"></div>
</div>
<div class="js-address-validation-field" data-address-field="postal">
<label for="address-validation-postal">Postal code</label>
<input id="address-validation-postal" name="postal" type="text" placeholder="Choose country first">
<div class="js-address-validation-message" data-address-message="postal"></div>
</div>
<div class="js-address-validation-field is-full" data-address-field="notes">
<label for="address-validation-notes">Delivery notes</label>
<textarea id="address-validation-notes" name="notes" placeholder="Apartment, floor, door code, or delivery instructions..."></textarea>
<div class="js-address-validation-message" data-address-message="notes"></div>
</div>
</div>
<div class="js-address-validation-hint" data-js-address-hint>Choose a country to see the postal code rule.</div>
<button class="js-address-validation-submit" type="submit">Validate Address</button>
<div class="js-address-validation-result" data-js-address-result>
Address validation passed. This shipping address can now be sent to checkout, CRM, or delivery systems.
</div>
</form>
</div>
<aside class="js-address-validation-summary">
<div>
<div class="js-address-validation-summary-kicker">Live Summary</div>
<h3>Delivery preview</h3>
<p>The summary updates while users type, making checkout and shipping forms feel clearer.</p>
</div>
<div class="js-address-validation-box">
<div class="js-address-validation-line">
<span>Recipient</span>
<strong data-js-address-preview-name>Recipient name</strong>
</div>
<div class="js-address-validation-line">
<span>Street</span>
<strong data-js-address-preview-street>Street address</strong>
</div>
<div class="js-address-validation-line">
<span>City / Country</span>
<strong data-js-address-preview-location>City, Country</strong>
</div>
<div class="js-address-validation-line">
<span>Postal code</span>
<strong data-js-address-preview-postal>Postal code</strong>
</div>
</div>
<div class="js-address-validation-delivery">
<div class="js-address-validation-truck">🚚</div>
<div>
<strong>Delivery-ready data</strong>
<span>Validate address fields before order submission.</span>
</div>
</div>
</aside>
</div>
CSS
.js-address-validation-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 26px;
align-items: stretch;
min-height: 660px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 14% 16%, rgba(34, 197, 94, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(245, 158, 11, 0.14), transparent 34%),
linear-gradient(135deg, #f0fdf4 0%, #ffffff 52%, #fffbeb 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
/* Full CSS is included in the live preview block above. Copy the full style block from the live example for production use. */
This address form validation example is useful for ecommerce checkout pages, delivery forms, shipping calculators, booking forms, billing forms, customer profile pages, and order request flows.
15. Terms and Conditions Checkbox Validation
Terms and conditions checkbox validation is a common requirement in signup forms, checkout forms, trial forms, booking forms, and account creation flows. This pattern keeps the submit button disabled until the user accepts the required agreement.
This example uses a clear consent card with a required checkbox, disabled/enabled button state, inline validation message, and a success state. It is intentionally focused on one validation rule so users can clearly see how required consent validation works.
- Validates a required terms and conditions checkbox
- Keeps the submit button disabled until consent is accepted
- Shows clear accepted and missing-consent messages
- Uses a focused legal agreement card layout
- Good for signup forms, checkout forms, account forms, and trial registration
Create account
Complete the short form and accept the required terms before submitting.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-terms-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-terms-validation-form]");
const agreement = demo.querySelector("[data-js-terms-agreement]");
const submitButton = demo.querySelector("[data-js-terms-submit]");
const result = demo.querySelector("[data-js-terms-result]");
const statusIcon = demo.querySelector("[data-js-terms-status-icon]");
const statusTitle = demo.querySelector("[data-js-terms-status-title]");
const statusText = demo.querySelector("[data-js-terms-status-text]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
terms: form.querySelector("[name='terms']")
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-terms-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-terms-message='" + fieldName + "']");
if (!wrap || !messageBox) {
return;
}
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateTerms() {
const messageBox = demo.querySelector("[data-terms-message='terms']");
const isAccepted = fields.terms.checked;
agreement.classList.remove("is-valid", "is-error");
agreement.classList.add(isAccepted ? "is-valid" : "is-error");
if (isAccepted) {
messageBox.textContent = "Agreement accepted. You can submit the form.";
statusIcon.textContent = "✓";
statusTitle.textContent = "Ready to submit";
statusText.textContent = "The required agreement has been accepted.";
submitButton.disabled = false;
return true;
}
messageBox.textContent = "Please accept the terms before submitting.";
statusIcon.textContent = "!";
statusTitle.textContent = "Agreement required";
statusText.textContent = "The form cannot be submitted until the checkbox is accepted.";
submitButton.disabled = true;
return false;
}
function validateAll() {
return validateName() && validateEmail() && validateTerms();
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.terms.addEventListener("change", function () {
validateTerms();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
validateTerms();
});
})();
HTML
<div class="js-terms-validation-preview" data-js-terms-validation-demo>
<aside class="js-terms-validation-info">
<div>
<div class="js-terms-validation-kicker">Consent Validation</div>
<h3>Make required agreements clear before submit</h3>
<p>Use a focused consent validation pattern when your form requires users to accept terms, privacy notices, or service policies.</p>
</div>
<div class="js-terms-validation-points">
<div class="js-terms-validation-point">
<div class="js-terms-validation-point-icon">✓</div>
<div>
<strong>Required agreement</strong>
<span>The submit button is locked until accepted.</span>
</div>
</div>
<div class="js-terms-validation-point">
<div class="js-terms-validation-point-icon">↗</div>
<div>
<strong>Clear links</strong>
<span>Terms and privacy links are easy to see.</span>
</div>
</div>
<div class="js-terms-validation-point">
<div class="js-terms-validation-point-icon">!</div>
<div>
<strong>Inline feedback</strong>
<span>Users immediately understand what is missing.</span>
</div>
</div>
</div>
</aside>
<div class="js-terms-validation-card">
<h3>Create account</h3>
<p class="js-terms-validation-text">Complete the short form and accept the required terms before submitting.</p>
<form class="js-terms-validation-form" novalidate data-js-terms-validation-form>
<div class="js-terms-validation-user-grid">
<div class="js-terms-validation-field" data-terms-field="name">
<label for="terms-validation-name">Name</label>
<input id="terms-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-terms-validation-message" data-terms-message="name"></div>
</div>
<div class="js-terms-validation-field" data-terms-field="email">
<label for="terms-validation-email">Email</label>
<input id="terms-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-terms-validation-message" data-terms-message="email"></div>
</div>
</div>
<div class="js-terms-validation-agreement" data-js-terms-agreement>
<input id="terms-validation-checkbox" name="terms" type="checkbox">
<label for="terms-validation-checkbox">
I agree to the <a href="#">Terms and Conditions</a> and confirm that I have read the <a href="#">Privacy Policy</a>.
</label>
<div class="js-terms-validation-agreement-message" data-terms-message="terms">Accept the agreement to unlock the submit button.</div>
</div>
<div class="js-terms-validation-status">
<div class="js-terms-validation-status-icon" data-js-terms-status-icon>!</div>
<div>
<strong data-js-terms-status-title>Agreement required</strong>
<span data-js-terms-status-text>The form cannot be submitted until the checkbox is accepted.</span>
</div>
</div>
<button class="js-terms-validation-submit" type="submit" data-js-terms-submit disabled>Create Account</button>
<div class="js-terms-validation-result" data-js-terms-result>
Terms validation passed. This account form can now be connected to your backend registration flow.
</div>
</form>
</div>
</div>
CSS
.js-terms-validation-preview {
display: grid;
grid-template-columns: minmax(0, 0.95fr) minmax(340px, 1.05fr);
gap: 28px;
align-items: stretch;
min-height: 620px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.14), transparent 34%),
linear-gradient(135deg, #eef2ff 0%, #ffffff 52%, #f0f9ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-terms-validation-info {
position: relative;
overflow: hidden;
padding: 34px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #1e1b4b 0%, #4338ca 48%, #0284c7 100%);
box-shadow: 0 26px 80px rgba(67, 56, 202, 0.25);
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 28px;
}
.js-terms-validation-info::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 290px;
height: 290px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-terms-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #e0e7ff;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-terms-validation-info h3 {
position: relative;
z-index: 2;
max-width: 520px;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(38px, 5vw, 64px);
line-height: 1;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-terms-validation-info p {
position: relative;
z-index: 2;
max-width: 500px;
margin: 0;
color: #dbeafe;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-terms-validation-points {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
}
.js-terms-validation-point {
display: grid;
grid-template-columns: 44px 1fr;
gap: 12px;
align-items: center;
padding: 14px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-terms-validation-point-icon {
display: grid;
place-items: center;
width: 44px;
height: 44px;
border-radius: 16px;
background: rgba(255, 255, 255, 0.16);
color: #ffffff;
font-size: 19px;
}
.js-terms-validation-point strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 14px;
line-height: 1.2;
font-weight: 950;
}
.js-terms-validation-point span {
display: block;
color: #dbeafe;
font-size: 12px;
line-height: 1.4;
font-weight: 700;
}
.js-terms-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
display: flex;
flex-direction: column;
justify-content: center;
}
.js-terms-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-terms-validation-text {
margin: 0 0 22px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-terms-validation-form {
display: grid;
gap: 16px;
}
.js-terms-validation-user-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-terms-validation-field {
display: grid;
gap: 7px;
}
.js-terms-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-terms-validation-field input[type="text"],
.js-terms-validation-field input[type="email"] {
width: 100%;
box-sizing: border-box;
min-height: 52px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 17px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-terms-validation-field input[type="text"]:focus,
.js-terms-validation-field input[type="email"]:focus {
background: #ffffff;
border-color: rgba(67, 56, 202, 0.62);
box-shadow: 0 0 0 4px rgba(67, 56, 202, 0.11);
}
.js-terms-validation-field.is-error input[type="text"],
.js-terms-validation-field.is-error input[type="email"] {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-terms-validation-field.is-valid input[type="text"],
.js-terms-validation-field.is-valid input[type="email"] {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-terms-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 750;
}
.js-terms-validation-field.is-valid .js-terms-validation-message {
color: #15803d;
}
.js-terms-validation-agreement {
display: grid;
grid-template-columns: 24px 1fr;
gap: 12px;
padding: 16px;
border-radius: 22px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.09);
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-terms-validation-agreement.is-error {
background: #fff1f2;
border-color: rgba(239, 68, 68, 0.28);
}
.js-terms-validation-agreement.is-valid {
background: #f0fdf4;
border-color: rgba(34, 197, 94, 0.26);
}
.js-terms-validation-agreement input {
width: 20px;
height: 20px;
margin: 2px 0 0;
accent-color: #4f46e5;
}
.js-terms-validation-agreement label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-terms-validation-agreement a {
color: #4338ca !important;
-webkit-text-fill-color: #4338ca !important;
font-weight: 950;
text-decoration: none !important;
}
.js-terms-validation-agreement a:hover {
text-decoration: underline !important;
}
.js-terms-validation-agreement-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-terms-validation-agreement.is-valid .js-terms-validation-agreement-message {
color: #15803d;
}
.js-terms-validation-status {
display: grid;
grid-template-columns: 44px 1fr;
gap: 12px;
align-items: center;
padding: 14px;
border-radius: 20px;
background: #eef2ff;
border: 1px solid rgba(67, 56, 202, 0.13);
}
.js-terms-validation-status-icon {
display: grid;
place-items: center;
width: 44px;
height: 44px;
border-radius: 16px;
background: #e0e7ff;
color: #4338ca;
font-size: 20px;
font-weight: 950;
}
.js-terms-validation-status strong {
display: block;
margin-bottom: 3px;
color: #111827;
font-size: 14px;
font-weight: 950;
line-height: 1.2;
}
.js-terms-validation-status span {
display: block;
color: #64748b;
font-size: 12px;
line-height: 1.4;
font-weight: 750;
}
.js-terms-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #4f46e5, #0284c7);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(67, 56, 202, 0.25);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease, opacity 0.2s ease;
}
.js-terms-validation-submit:disabled {
cursor: not-allowed;
opacity: 0.52;
box-shadow: none;
transform: none;
filter: grayscale(0.2);
}
.js-terms-validation-submit:not(:disabled):hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(67, 56, 202, 0.34);
}
.js-terms-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-terms-validation-result.is-visible {
display: block;
}
@media (max-width: 940px) {
.js-terms-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-terms-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-terms-validation-info,
.js-terms-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-terms-validation-user-grid {
grid-template-columns: 1fr;
}
.js-terms-validation-info h3,
.js-terms-validation-card h3 {
font-size: 30px;
}
}
This terms and conditions checkbox validation pattern is useful for account creation, checkout forms, free trial forms, booking forms, newsletter forms with consent, SaaS onboarding, and any form where users must accept a required policy before submitting.
16. Search Form Validation
Search form validation helps prevent empty searches, very short queries, and unwanted characters before a search request is sent. This is useful for site search, product search, documentation search, help center search, blog search, and dashboard filters.
This example validates a search query before submit, blocks short queries, removes repeated spaces, checks for spam-like symbols, and shows suggested quick searches. The design uses a modern search panel with result preview cards.
- Validates search input before submit
- Requires at least 3 useful characters
- Blocks repeated spam symbols
- Includes quick suggestion buttons
- Good for site search, ecommerce search, help centers, blogs, and dashboards
Validate search queries before showing results
Prevent empty searches, very short queries, and spam-like characters before sending a search request to your website, store, help center, or dashboard.
Use JavaScript to validate required fields, emails, passwords, files, and checkout details.
Block empty input, short keywords, and repeated symbol patterns before search.
Quick suggestions help users search faster and reduce invalid search attempts.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-search-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-search-validation-form]");
const input = form.querySelector("[name='search']");
const inputWrap = demo.querySelector("[data-js-search-input-wrap]");
const message = demo.querySelector("[data-js-search-message]");
const output = demo.querySelector("[data-js-search-output]");
const suggestions = demo.querySelectorAll("[data-search-suggestion]");
function cleanQuery(value) {
return value.replace(/\s+/g, " ").trim();
}
function hasSpamSymbols(value) {
return /([!@#$%^&*_=+<>?])\1{2,}/.test(value);
}
function setSearchState(isValid, text) {
inputWrap.classList.remove("is-valid", "is-error");
inputWrap.classList.add(isValid ? "is-valid" : "is-error");
message.textContent = text;
message.classList.toggle("is-valid", isValid);
}
function validateSearch(showOutput) {
const cleaned = cleanQuery(input.value);
input.value = cleaned;
if (!cleaned) {
setSearchState(false, "Please enter a search query.");
output.classList.remove("is-visible");
return false;
}
if (cleaned.length < 3) {
setSearchState(false, "Search query must be at least 3 characters.");
output.classList.remove("is-visible");
return false;
}
if (hasSpamSymbols(cleaned)) {
setSearchState(false, "Remove repeated symbols before searching.");
output.classList.remove("is-visible");
return false;
}
setSearchState(true, "Search query looks valid: “" + cleaned + "”");
if (showOutput) {
output.textContent = "Search validation passed for: “" + cleaned + "”. In production, this query can now be sent to your search endpoint.";
output.classList.add("is-visible");
}
return true;
}
input.addEventListener("input", function () {
output.classList.remove("is-visible");
if (input.value.trim().length === 0) {
inputWrap.classList.remove("is-valid", "is-error");
message.textContent = "Type at least 3 characters to search.";
message.classList.remove("is-valid");
return;
}
validateSearch(false);
});
suggestions.forEach(function (button) {
button.addEventListener("click", function () {
input.value = button.dataset.searchSuggestion;
validateSearch(true);
});
});
form.addEventListener("submit", function (event) {
event.preventDefault();
validateSearch(true);
});
});
})();
HTML
<div class="js-search-validation-preview" data-js-search-validation-demo>
<div class="js-search-validation-shell">
<div class="js-search-validation-hero">
<div class="js-search-validation-kicker">Search Validation</div>
<h3>Validate search queries before showing results</h3>
<p>Prevent empty searches, very short queries, and spam-like characters before sending a search request to your website, store, help center, or dashboard.</p>
</div>
<div class="js-search-validation-card">
<form class="js-search-validation-form" novalidate data-js-search-validation-form>
<div class="js-search-validation-row">
<div class="js-search-validation-input-wrap" data-js-search-input-wrap>
<input name="search" type="search" placeholder="Search products, articles, docs..." autocomplete="off">
<span class="js-search-validation-icon">⌕</span>
</div>
<button class="js-search-validation-submit" type="submit">Search</button>
</div>
<div class="js-search-validation-message" data-js-search-message>Type at least 3 characters to search.</div>
<div class="js-search-validation-suggestions">
<button class="js-search-validation-suggestion" type="button" data-search-suggestion="checkout validation">checkout validation</button>
<button class="js-search-validation-suggestion" type="button" data-search-suggestion="email form">email form</button>
<button class="js-search-validation-suggestion" type="button" data-search-suggestion="booking calendar">booking calendar</button>
<button class="js-search-validation-suggestion" type="button" data-search-suggestion="upload field">upload field</button>
</div>
<div class="js-search-validation-output" data-js-search-output>
Search validation passed. In production, this query can now be sent to your search endpoint.
</div>
</form>
</div>
<div class="js-search-validation-results">
<div class="js-search-validation-result-card">
<span>Guide</span>
<strong>Form validation patterns</strong>
<p>Use JavaScript to validate required fields, emails, passwords, files, and checkout details.</p>
</div>
<div class="js-search-validation-result-card">
<span>Example</span>
<strong>Search query rules</strong>
<p>Block empty input, short keywords, and repeated symbol patterns before search.</p>
</div>
<div class="js-search-validation-result-card">
<span>UX Tip</span>
<strong>Use suggestions</strong>
<p>Quick suggestions help users search faster and reduce invalid search attempts.</p>
</div>
</div>
</div>
</div>
CSS
.js-search-validation-preview {
min-height: 660px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(6, 182, 212, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(16, 185, 129, 0.14), transparent 34%),
linear-gradient(135deg, #ecfeff 0%, #ffffff 52%, #ecfdf5 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-search-validation-shell {
width: min(100%, 1080px);
margin: 0 auto;
display: grid;
gap: 26px;
}
.js-search-validation-hero {
position: relative;
overflow: hidden;
padding: 34px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #083344 0%, #0891b2 48%, #059669 100%);
box-shadow: 0 26px 80px rgba(8, 145, 178, 0.24);
}
.js-search-validation-hero::before {
content: "";
position: absolute;
right: -120px;
top: -120px;
width: 290px;
height: 290px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-search-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #cffafe;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-search-validation-hero h3 {
position: relative;
z-index: 2;
max-width: 780px;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(38px, 5vw, 68px);
line-height: 1;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-search-validation-hero p {
position: relative;
z-index: 2;
max-width: 760px;
margin: 0;
color: #cffafe;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-search-validation-card {
padding: 30px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-search-validation-form {
display: grid;
gap: 16px;
}
.js-search-validation-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12px;
}
.js-search-validation-input-wrap {
position: relative;
}
.js-search-validation-input-wrap input {
width: 100%;
box-sizing: border-box;
min-height: 60px;
padding: 15px 52px 15px 18px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 22px;
background: #f8fafc;
color: #111827;
font-size: 17px;
line-height: 1.4;
font-weight: 750;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-search-validation-input-wrap input:focus {
background: #ffffff;
border-color: rgba(8, 145, 178, 0.62);
box-shadow: 0 0 0 4px rgba(8, 145, 178, 0.11);
}
.js-search-validation-input-wrap.is-error input {
background: #fff1f2;
border-color: rgba(239, 68, 68, 0.62);
}
.js-search-validation-input-wrap.is-valid input {
background: #f0fdf4;
border-color: rgba(34, 197, 94, 0.58);
}
.js-search-validation-icon {
position: absolute;
top: 50%;
right: 17px;
transform: translateY(-50%);
color: #64748b;
font-size: 22px;
pointer-events: none;
}
.js-search-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 60px;
padding: 16px 24px;
border: 0;
border-radius: 22px;
background: linear-gradient(135deg, #0891b2, #059669);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(8, 145, 178, 0.25);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-search-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(8, 145, 178, 0.34);
}
.js-search-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-search-validation-message.is-valid {
color: #15803d;
}
.js-search-validation-suggestions {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.js-search-validation-suggestion {
display: inline-flex;
align-items: center;
min-height: 38px;
padding: 10px 13px;
border: 1px solid rgba(8, 145, 178, 0.18);
border-radius: 999px;
background: #ecfeff;
color: #0e7490;
font-size: 13px;
line-height: 1;
font-weight: 900;
cursor: pointer;
transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.js-search-validation-suggestion:hover {
transform: translateY(-1px);
background: #cffafe;
border-color: rgba(8, 145, 178, 0.32);
}
.js-search-validation-results {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.js-search-validation-result-card {
min-height: 150px;
padding: 18px;
border-radius: 24px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 18px 48px rgba(15, 23, 42, 0.09);
}
.js-search-validation-result-card strong {
display: block;
margin-bottom: 8px;
color: #111827;
font-size: 17px;
line-height: 1.2;
font-weight: 950;
letter-spacing: -0.025em;
}
.js-search-validation-result-card p {
margin: 0;
color: #64748b;
font-size: 14px;
line-height: 1.6;
font-weight: 650;
}
.js-search-validation-result-card span {
display: inline-flex;
margin-bottom: 12px;
padding: 7px 10px;
border-radius: 999px;
background: #f0fdfa;
color: #0f766e;
font-size: 12px;
line-height: 1;
font-weight: 950;
}
.js-search-validation-output {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-search-validation-output.is-visible {
display: block;
}
@media (max-width: 900px) {
.js-search-validation-results {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-search-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-search-validation-hero,
.js-search-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-search-validation-row {
grid-template-columns: 1fr;
}
.js-search-validation-hero h3 {
font-size: 32px;
}
}
This search form validation example is useful for ecommerce product search, website search, blog search, documentation search, help center search, dashboard filters, and any interface where users need guidance before sending a query.
17. Coupon Code Validation
Coupon code validation is useful for ecommerce checkout pages, pricing tables, SaaS plans, booking forms, course checkout pages, and landing pages with promotional offers. A good coupon field should format the code, validate the rule, show an invalid state, and preview the discount before checkout continues.
This example converts coupon codes to uppercase, removes extra spaces, checks demo coupon rules, displays invalid coupon feedback, and updates the order summary when a valid code is applied.
- Validates coupon code format with JavaScript
- Automatically converts coupon codes to uppercase
- Shows discount preview for valid codes
- Displays invalid, expired, and minimum-order messages
- Good for ecommerce checkout, SaaS pricing, booking forms, and promo landing pages
Order summary
Try SAVE10, VIP25, EXPIRED, or a wrong code.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-coupon-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-coupon-validation-form]");
const field = demo.querySelector("[data-coupon-field]");
const input = form.querySelector("[name='coupon']");
const message = demo.querySelector("[data-coupon-message]");
const result = demo.querySelector("[data-js-coupon-result]");
const removeButton = demo.querySelector("[data-js-coupon-remove]");
const fillButtons = demo.querySelectorAll("[data-coupon-fill]");
const discountRow = demo.querySelector("[data-js-coupon-discount-row]");
const discountLabel = demo.querySelector("[data-js-coupon-discount-label]");
const discountValue = demo.querySelector("[data-js-coupon-discount-value]");
const totalValue = demo.querySelector("[data-js-coupon-total]");
const subtotal = 120;
const shipping = 12;
const coupons = {
SAVE10: {
type: "percent",
amount: 10,
minimum: 0,
label: "SAVE10 discount"
},
VIP25: {
type: "percent",
amount: 25,
minimum: 150,
label: "VIP25 discount"
},
FREESHIP: {
type: "shipping",
amount: 12,
minimum: 0,
label: "Free shipping"
},
EXPIRED: {
expired: true,
label: "Expired coupon"
}
};
function formatMoney(value) {
return "$" + value.toFixed(2);
}
function normalizeCoupon(value) {
return value.replace(/\s+/g, "").toUpperCase();
}
function resetDiscount() {
discountRow.classList.remove("is-visible");
discountLabel.textContent = "Discount";
discountValue.textContent = "-$0.00";
totalValue.textContent = formatMoney(subtotal + shipping);
removeButton.classList.remove("is-visible");
result.classList.remove("is-visible");
}
function setState(isValid, text) {
field.classList.remove("is-valid", "is-error");
field.classList.add(isValid ? "is-valid" : "is-error");
message.textContent = text;
}
function applyCoupon() {
const code = normalizeCoupon(input.value);
input.value = code;
resetDiscount();
if (!code) {
setState(false, "Please enter a coupon code.");
return false;
}
if (!/^[A-Z0-9]{4,12}$/.test(code)) {
setState(false, "Coupon codes can use 4-12 letters or numbers.");
return false;
}
if (!coupons[code]) {
setState(false, "This coupon code is not valid.");
return false;
}
const coupon = coupons[code];
if (coupon.expired) {
setState(false, "This coupon code has expired.");
return false;
}
if (subtotal < coupon.minimum) {
setState(false, code + " requires a minimum order of " + formatMoney(coupon.minimum) + ".");
return false;
}
let discount = 0;
if (coupon.type === "percent") {
discount = subtotal * (coupon.amount / 100);
}
if (coupon.type === "shipping") {
discount = Math.min(shipping, coupon.amount);
}
const total = subtotal + shipping - discount;
discountLabel.textContent = coupon.label;
discountValue.textContent = "-" + formatMoney(discount);
totalValue.textContent = formatMoney(total);
discountRow.classList.add("is-visible");
removeButton.classList.add("is-visible");
result.classList.add("is-visible");
setState(true, code + " applied successfully.");
return true;
}
input.addEventListener("input", function () {
input.value = normalizeCoupon(input.value);
resetDiscount();
if (!input.value) {
field.classList.remove("is-valid", "is-error");
message.textContent = "Enter a coupon code to apply a discount.";
return;
}
if (!/^[A-Z0-9]{0,12}$/.test(input.value)) {
setState(false, "Use only letters and numbers.");
} else {
message.textContent = "Click Apply to validate this coupon.";
field.classList.remove("is-valid", "is-error");
}
});
fillButtons.forEach(function (button) {
button.addEventListener("click", function () {
input.value = button.dataset.couponFill;
applyCoupon();
});
});
removeButton.addEventListener("click", function () {
input.value = "";
resetDiscount();
field.classList.remove("is-valid", "is-error");
message.textContent = "Coupon removed. Enter another code if you have one.";
});
form.addEventListener("submit", function (event) {
event.preventDefault();
applyCoupon();
});
resetDiscount();
});
})();
HTML
<div class="js-coupon-validation-preview" data-js-coupon-validation-demo>
<aside class="js-coupon-validation-main">
<div>
<div class="js-coupon-validation-kicker">Coupon Validation</div>
<h3>Apply promo codes with instant checkout feedback</h3>
<p>Validate discount codes before the user continues to payment, booking, subscription, or order confirmation.</p>
</div>
<div class="js-coupon-validation-offers">
<div class="js-coupon-validation-offer">
<strong>SAVE10</strong>
<span>10% off any order</span>
</div>
<div class="js-coupon-validation-offer">
<strong>VIP25</strong>
<span>25% off orders over $150</span>
</div>
<div class="js-coupon-validation-offer">
<strong>EXPIRED</strong>
<span>Demo expired code</span>
</div>
</div>
</aside>
<div class="js-coupon-validation-card">
<h3>Order summary</h3>
<p class="js-coupon-validation-text">Try <strong>SAVE10</strong>, <strong>VIP25</strong>, <strong>EXPIRED</strong>, or a wrong code.</p>
<div class="js-coupon-validation-summary">
<div class="js-coupon-validation-row">
<span>Subtotal</span>
<strong data-js-coupon-subtotal>$120.00</strong>
</div>
<div class="js-coupon-validation-row">
<span>Shipping</span>
<strong>$12.00</strong>
</div>
<div class="js-coupon-validation-row is-discount" data-js-coupon-discount-row>
<span data-js-coupon-discount-label>Discount</span>
<strong data-js-coupon-discount-value>-$0.00</strong>
</div>
<div class="js-coupon-validation-row js-coupon-validation-total">
<span>Total</span>
<strong data-js-coupon-total>$132.00</strong>
</div>
</div>
<form class="js-coupon-validation-form" novalidate data-js-coupon-validation-form>
<div class="js-coupon-validation-field" data-coupon-field>
<label for="coupon-validation-code">Coupon code</label>
<div class="js-coupon-validation-input-row">
<input id="coupon-validation-code" name="coupon" type="text" placeholder="SAVE10" autocomplete="off">
<button class="js-coupon-validation-apply" type="submit">Apply</button>
</div>
<div class="js-coupon-validation-message" data-coupon-message>Enter a coupon code to apply a discount.</div>
</div>
<div class="js-coupon-validation-hints">
<button class="js-coupon-validation-hint" type="button" data-coupon-fill="SAVE10">Try SAVE10</button>
<button class="js-coupon-validation-hint" type="button" data-coupon-fill="VIP25">Try VIP25</button>
<button class="js-coupon-validation-hint" type="button" data-coupon-fill="EXPIRED">Try EXPIRED</button>
</div>
<button class="js-coupon-validation-remove" type="button" data-js-coupon-remove>Remove coupon</button>
<div class="js-coupon-validation-result" data-js-coupon-result>
Coupon validation passed. The order total has been updated.
</div>
</form>
</div>
</div>
CSS
.js-coupon-validation-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 370px;
gap: 26px;
align-items: stretch;
min-height: 660px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(244, 63, 94, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(249, 115, 22, 0.16), transparent 34%),
linear-gradient(135deg, #fff1f2 0%, #ffffff 52%, #fff7ed 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-coupon-validation-main {
position: relative;
overflow: hidden;
padding: 34px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.22), transparent 34%),
linear-gradient(135deg, #881337 0%, #e11d48 48%, #f97316 100%);
box-shadow: 0 26px 80px rgba(225, 29, 72, 0.24);
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 28px;
}
.js-coupon-validation-main::before {
content: "";
position: absolute;
right: -120px;
top: -120px;
width: 290px;
height: 290px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-coupon-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #ffe4e6;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-coupon-validation-main h3 {
position: relative;
z-index: 2;
max-width: 610px;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(38px, 5vw, 68px);
line-height: 0.98;
font-weight: 950;
letter-spacing: -0.08em;
}
.js-coupon-validation-main p {
position: relative;
z-index: 2;
max-width: 590px;
margin: 0;
color: #ffe4e6;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-coupon-validation-offers {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px;
}
.js-coupon-validation-offer {
padding: 16px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-coupon-validation-offer strong {
display: block;
margin-bottom: 6px;
color: #ffffff;
font-size: 18px;
line-height: 1.1;
font-weight: 950;
letter-spacing: -0.02em;
}
.js-coupon-validation-offer span {
display: block;
color: #ffe4e6;
font-size: 12px;
line-height: 1.4;
font-weight: 750;
}
.js-coupon-validation-card {
padding: 30px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
display: flex;
flex-direction: column;
justify-content: center;
}
.js-coupon-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 34px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.055em;
}
.js-coupon-validation-text {
margin: 0 0 22px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-coupon-validation-summary {
display: grid;
gap: 12px;
margin-bottom: 18px;
padding: 18px;
border-radius: 24px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-coupon-validation-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
color: #334155;
font-size: 14px;
line-height: 1.3;
font-weight: 800;
}
.js-coupon-validation-row strong {
color: #111827;
font-weight: 950;
}
.js-coupon-validation-row.is-discount {
display: none;
color: #15803d;
}
.js-coupon-validation-row.is-discount.is-visible {
display: flex;
}
.js-coupon-validation-total {
padding-top: 12px;
border-top: 1px solid rgba(15, 23, 42, 0.10);
font-size: 18px;
font-weight: 950;
}
.js-coupon-validation-total strong {
font-size: 24px;
color: #e11d48;
}
.js-coupon-validation-form {
display: grid;
gap: 14px;
}
.js-coupon-validation-field {
display: grid;
gap: 7px;
}
.js-coupon-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-coupon-validation-input-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 10px;
}
.js-coupon-validation-field input {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-coupon-validation-field input:focus {
background: #ffffff;
border-color: rgba(225, 29, 72, 0.62);
box-shadow: 0 0 0 4px rgba(225, 29, 72, 0.11);
}
.js-coupon-validation-field.is-error input {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-coupon-validation-field.is-valid input {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-coupon-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-coupon-validation-field.is-valid .js-coupon-validation-message {
color: #15803d;
}
.js-coupon-validation-apply,
.js-coupon-validation-remove {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 15px 18px;
border: 0;
border-radius: 18px;
font-size: 14px;
line-height: 1;
font-weight: 950;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-coupon-validation-apply {
background: linear-gradient(135deg, #e11d48, #f97316);
color: #ffffff;
box-shadow: 0 18px 42px rgba(225, 29, 72, 0.24);
}
.js-coupon-validation-apply:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(225, 29, 72, 0.32);
}
.js-coupon-validation-remove {
display: none;
width: fit-content;
min-height: 40px;
padding: 12px 14px;
border-radius: 999px;
background: #fee2e2;
color: #b91c1c;
}
.js-coupon-validation-remove.is-visible {
display: inline-flex;
}
.js-coupon-validation-hints {
display: flex;
flex-wrap: wrap;
gap: 9px;
}
.js-coupon-validation-hint {
display: inline-flex;
align-items: center;
min-height: 34px;
padding: 9px 11px;
border: 1px solid rgba(225, 29, 72, 0.16);
border-radius: 999px;
background: #fff1f2;
color: #be123c;
font-size: 12px;
line-height: 1;
font-weight: 900;
cursor: pointer;
}
.js-coupon-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-coupon-validation-result.is-visible {
display: block;
}
@media (max-width: 940px) {
.js-coupon-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-coupon-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-coupon-validation-main,
.js-coupon-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-coupon-validation-offers {
grid-template-columns: 1fr;
}
.js-coupon-validation-input-row {
grid-template-columns: 1fr;
}
.js-coupon-validation-main h3,
.js-coupon-validation-card h3 {
font-size: 30px;
}
}
This coupon code validation example is useful for ecommerce checkout pages, SaaS pricing pages, course checkout forms, booking discounts, promotional landing pages, and order summary sections where users need immediate feedback before payment.
18. Username Availability Validation UI
Username availability validation is useful for signup forms, profile settings, community platforms, SaaS dashboards, creator accounts, and membership websites. It helps users choose a valid username before account creation or profile update.
This example validates username length, allowed characters, blocked words, and simulates an availability check with a loading state. The availability result is front-end demo logic, but the same UI can be connected to a real backend endpoint.
- Validates username length and allowed characters
- Blocks reserved or unavailable usernames
- Simulates an async availability check
- Shows loading, available, unavailable, and invalid states
- Good for signup forms, profile pages, SaaS apps, and community websites
Choose username
Try names like admin, mart, webdesigner, or newcreator.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-username-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-username-validation-form]");
const input = form.querySelector("[name='username']");
const field = demo.querySelector("[data-username-field]");
const message = demo.querySelector("[data-username-message]");
const submitButton = demo.querySelector("[data-js-username-submit]");
const result = demo.querySelector("[data-js-username-result]");
const suggestions = demo.querySelector("[data-js-username-suggestions]");
const suggestionButtons = demo.querySelectorAll("[data-username-suggestion]");
const avatar = demo.querySelector("[data-js-username-avatar]");
const preview = demo.querySelector("[data-js-username-preview]");
const reservedNames = ["admin", "root", "support", "help", "billing", "moderator", "owner"];
const unavailableNames = ["mart", "john", "demo", "webdesigner", "creator", "designer"];
let availabilityTimer = null;
let currentAvailable = false;
function normalizeUsername(value) {
return value.toLowerCase().replace(/\s+/g, "").replace(/[^a-z0-9_]/g, "");
}
function setRule(name, state) {
const rule = demo.querySelector("[data-rule='" + name + "']");
rule.classList.remove("is-pass", "is-fail");
if (state === true) {
rule.classList.add("is-pass");
}
if (state === false) {
rule.classList.add("is-fail");
}
}
function setFieldState(state, text) {
field.classList.remove("is-valid", "is-error", "is-loading");
if (state) {
field.classList.add(state);
}
message.textContent = text;
}
function updatePreview(value) {
const clean = value || "yourname";
preview.textContent = "@" + clean;
avatar.textContent = clean.charAt(0).toUpperCase();
}
function resetAvailability() {
currentAvailable = false;
submitButton.disabled = true;
result.classList.remove("is-visible");
suggestions.classList.remove("is-visible");
setRule("available", null);
}
function validateStaticRules(value) {
const hasLength = value.length >= 4 && value.length <= 16;
const hasCharacters = /^[a-z0-9_]+$/.test(value);
const isReserved = reservedNames.includes(value);
setRule("length", value ? hasLength : null);
setRule("characters", value ? hasCharacters : null);
setRule("reserved", value ? !isReserved : null);
if (!value) {
setFieldState("", "Use 4-16 characters, letters, numbers, and underscores only.");
resetAvailability();
return false;
}
if (!hasLength) {
setFieldState("is-error", "Username must be 4-16 characters.");
resetAvailability();
return false;
}
if (!hasCharacters) {
setFieldState("is-error", "Use only letters, numbers, and underscores.");
resetAvailability();
return false;
}
if (isReserved) {
setFieldState("is-error", "This username is reserved.");
resetAvailability();
return false;
}
return true;
}
function checkAvailability(value) {
window.clearTimeout(availabilityTimer);
currentAvailable = false;
submitButton.disabled = true;
setFieldState("is-loading", "Checking username availability...");
setRule("available", null);
suggestions.classList.remove("is-visible");
availabilityTimer = window.setTimeout(function () {
const isUnavailable = unavailableNames.includes(value);
if (isUnavailable) {
currentAvailable = false;
setFieldState("is-error", "This username is already taken.");
setRule("available", false);
suggestions.classList.add("is-visible");
submitButton.disabled = true;
return;
}
currentAvailable = true;
setFieldState("is-valid", "Username is available.");
setRule("available", true);
suggestions.classList.remove("is-visible");
submitButton.disabled = false;
}, 650);
}
function validateUsername() {
const clean = normalizeUsername(input.value);
input.value = clean;
updatePreview(clean);
if (!validateStaticRules(clean)) {
return false;
}
checkAvailability(clean);
return true;
}
input.addEventListener("input", function () {
validateUsername();
});
suggestionButtons.forEach(function (button) {
button.addEventListener("click", function () {
input.value = button.dataset.usernameSuggestion;
validateUsername();
});
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (currentAvailable) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
validateUsername();
}
});
updatePreview("");
});
})();
HTML
<div class="js-username-validation-preview" data-js-username-validation-demo>
<aside class="js-username-validation-profile">
<div>
<div class="js-username-validation-badge">Username Check</div>
<h3>Help users choose an available username</h3>
<p>Validate username rules before sending account creation or profile update data to your backend.</p>
</div>
<div class="js-username-validation-avatar-card">
<div class="js-username-validation-avatar" data-js-username-avatar>M</div>
</div>
<div class="js-username-validation-preview-name">
<span>Profile URL</span>
<strong data-js-username-preview>@yourname</strong>
</div>
</aside>
<div class="js-username-validation-card">
<h3>Choose username</h3>
<p class="js-username-validation-text">Try names like <strong>admin</strong>, <strong>mart</strong>, <strong>webdesigner</strong>, or <strong>newcreator</strong>.</p>
<form class="js-username-validation-form" novalidate data-js-username-validation-form>
<div class="js-username-validation-field" data-username-field>
<label for="username-validation-input">Username</label>
<div class="js-username-validation-input-wrap">
<span class="js-username-validation-prefix">@</span>
<input id="username-validation-input" name="username" type="text" placeholder="newcreator" autocomplete="off">
<span class="js-username-validation-icon" aria-hidden="true"></span>
</div>
<div class="js-username-validation-message" data-username-message>Use 4-16 characters, letters, numbers, and underscores only.</div>
</div>
<div class="js-username-validation-rules">
<div class="js-username-validation-rule" data-rule="length">4-16 characters</div>
<div class="js-username-validation-rule" data-rule="characters">Letters, numbers, and underscores only</div>
<div class="js-username-validation-rule" data-rule="reserved">Not a reserved username</div>
<div class="js-username-validation-rule" data-rule="available">Available to register</div>
</div>
<div class="js-username-validation-suggestions" data-js-username-suggestions>
<button class="js-username-validation-suggestion" type="button" data-username-suggestion="newcreator_24">@newcreator_24</button>
<button class="js-username-validation-suggestion" type="button" data-username-suggestion="webdesigner_pro">@webdesigner_pro</button>
<button class="js-username-validation-suggestion" type="button" data-username-suggestion="mart_studio">@mart_studio</button>
</div>
<button class="js-username-validation-submit" type="submit" data-js-username-submit disabled>Save Username</button>
<div class="js-username-validation-result" data-js-username-result>
Username validation passed. This username can now be saved to the user profile.
</div>
</form>
</div>
</div>
CSS
.js-username-validation-preview {
display: grid;
grid-template-columns: 360px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
min-height: 660px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(20, 184, 166, 0.14), transparent 34%),
linear-gradient(135deg, #eef2ff 0%, #ffffff 52%, #f0fdfa 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-username-validation-profile {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #312e81 0%, #4f46e5 48%, #0f766e 100%);
box-shadow: 0 26px 80px rgba(79, 70, 229, 0.24);
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 28px;
}
.js-username-validation-profile::before {
content: "";
position: absolute;
right: -110px;
top: -110px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-username-validation-badge {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #e0e7ff;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-username-validation-profile h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-username-validation-profile p {
position: relative;
z-index: 2;
margin: 0;
color: #dbeafe;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-username-validation-avatar-card {
position: relative;
z-index: 2;
display: grid;
place-items: center;
min-height: 240px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
}
.js-username-validation-avatar {
display: grid;
place-items: center;
width: 112px;
height: 112px;
border-radius: 36px;
background:
radial-gradient(circle at 30% 22%, rgba(255, 255, 255, 0.36), transparent 30%),
linear-gradient(135deg, #ffffff, #ccfbf1);
color: #4f46e5;
font-size: 46px;
line-height: 1;
font-weight: 950;
box-shadow: 0 28px 70px rgba(0, 0, 0, 0.20);
}
.js-username-validation-preview-name {
position: relative;
z-index: 2;
padding: 16px;
border-radius: 22px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-username-validation-preview-name span {
display: block;
margin-bottom: 6px;
color: #ccfbf1;
font-size: 11px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-username-validation-preview-name strong {
display: block;
overflow-wrap: anywhere;
color: #ffffff;
font-size: 22px;
line-height: 1.2;
font-weight: 950;
letter-spacing: -0.025em;
}
.js-username-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-username-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-username-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-username-validation-form {
display: grid;
gap: 16px;
}
.js-username-validation-field {
display: grid;
gap: 8px;
}
.js-username-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-username-validation-input-wrap {
position: relative;
}
.js-username-validation-prefix {
position: absolute;
top: 50%;
left: 16px;
transform: translateY(-50%);
color: #64748b;
font-size: 16px;
line-height: 1;
font-weight: 950;
pointer-events: none;
}
.js-username-validation-field input {
width: 100%;
box-sizing: border-box;
min-height: 58px;
padding: 15px 50px 15px 38px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 20px;
background: #f8fafc;
color: #111827;
font-size: 17px;
line-height: 1.4;
font-weight: 800;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-username-validation-field input:focus {
background: #ffffff;
border-color: rgba(79, 70, 229, 0.62);
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.11);
}
.js-username-validation-icon {
position: absolute;
top: 50%;
right: 15px;
transform: translateY(-50%);
display: none;
place-items: center;
width: 27px;
height: 27px;
border-radius: 999px;
font-size: 13px;
font-weight: 950;
}
.js-username-validation-field.is-valid input {
background: #f0fdf4;
border-color: rgba(34, 197, 94, 0.58);
}
.js-username-validation-field.is-error input {
background: #fff1f2;
border-color: rgba(239, 68, 68, 0.62);
}
.js-username-validation-field.is-loading input {
background: #eef2ff;
border-color: rgba(99, 102, 241, 0.42);
}
.js-username-validation-field.is-valid .js-username-validation-icon,
.js-username-validation-field.is-error .js-username-validation-icon,
.js-username-validation-field.is-loading .js-username-validation-icon {
display: grid;
}
.js-username-validation-field.is-valid .js-username-validation-icon {
background: #dcfce7;
color: #15803d;
}
.js-username-validation-field.is-valid .js-username-validation-icon::before {
content: "✓";
}
.js-username-validation-field.is-error .js-username-validation-icon {
background: #fee2e2;
color: #b91c1c;
}
.js-username-validation-field.is-error .js-username-validation-icon::before {
content: "!";
}
.js-username-validation-field.is-loading .js-username-validation-icon {
background: #e0e7ff;
color: #4f46e5;
}
.js-username-validation-field.is-loading .js-username-validation-icon::before {
content: "…";
}
.js-username-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-username-validation-field.is-valid .js-username-validation-message {
color: #15803d;
}
.js-username-validation-field.is-loading .js-username-validation-message {
color: #4f46e5;
}
.js-username-validation-rules {
display: grid;
gap: 10px;
padding: 16px;
border-radius: 22px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-username-validation-rule {
display: flex;
align-items: center;
gap: 10px;
color: #64748b;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-username-validation-rule::before {
content: "•";
display: grid;
place-items: center;
width: 22px;
height: 22px;
border-radius: 999px;
background: #e2e8f0;
color: #64748b;
font-size: 16px;
line-height: 1;
font-weight: 950;
}
.js-username-validation-rule.is-pass {
color: #15803d;
}
.js-username-validation-rule.is-pass::before {
content: "✓";
background: #dcfce7;
color: #15803d;
font-size: 12px;
}
.js-username-validation-rule.is-fail {
color: #dc2626;
}
.js-username-validation-rule.is-fail::before {
content: "!";
background: #fee2e2;
color: #dc2626;
font-size: 12px;
}
.js-username-validation-suggestions {
display: none;
gap: 9px;
flex-wrap: wrap;
}
.js-username-validation-suggestions.is-visible {
display: flex;
}
.js-username-validation-suggestion {
display: inline-flex;
align-items: center;
min-height: 36px;
padding: 9px 12px;
border: 1px solid rgba(79, 70, 229, 0.16);
border-radius: 999px;
background: #eef2ff;
color: #4338ca;
font-size: 12px;
line-height: 1;
font-weight: 900;
cursor: pointer;
}
.js-username-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #4f46e5, #0f766e);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(79, 70, 229, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease, opacity 0.2s ease;
}
.js-username-validation-submit:disabled {
opacity: 0.52;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.js-username-validation-submit:not(:disabled):hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(79, 70, 229, 0.33);
}
.js-username-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-username-validation-result.is-visible {
display: block;
}
@media (max-width: 940px) {
.js-username-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-username-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-username-validation-profile,
.js-username-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-username-validation-profile h3,
.js-username-validation-card h3 {
font-size: 30px;
}
}
This username availability validation UI is useful for signup forms, profile settings, membership websites, SaaS dashboards, marketplaces, creator platforms, and community apps where users need a unique public username.
19. Age Verification Form Validation
Age verification form validation is useful when a website needs to confirm that a user meets a minimum age requirement before continuing. This pattern is common in event registration, membership forms, booking forms, course enrollment, gated content, and account setup flows.
This example validates a date of birth field, blocks future dates, checks a minimum age of 18, updates an age preview card, and shows clear inline feedback before the form can be submitted.
- Validates date of birth with JavaScript
- Blocks future dates and empty date fields
- Calculates user age from the selected date
- Checks a minimum age requirement
- Good for registration forms, booking forms, memberships, events, and gated content
Verify your age
Select your date of birth and confirm that the information is correct before continuing.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-age-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-age-validation-form]");
const result = demo.querySelector("[data-js-age-result]");
const ageNumber = demo.querySelector("[data-js-age-number]");
const ageLabel = demo.querySelector("[data-js-age-label]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
birthday: form.querySelector("[name='birthday']"),
confirmAge: form.querySelector("[name='confirmAge']")
};
const minimumAge = 18;
const today = new Date();
const todayString = today.toISOString().split("T")[0];
fields.birthday.max = todayString;
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-age-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-age-message='" + fieldName + "']");
if (!messageBox) {
return;
}
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
function calculateAge(dateString) {
const birthDate = new Date(dateString);
const now = new Date();
let age = now.getFullYear() - birthDate.getFullYear();
const monthDiff = now.getMonth() - birthDate.getMonth();
const dayDiff = now.getDate() - birthDate.getDate();
if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
age--;
}
return age;
}
function updateAgePreview(age, label) {
ageNumber.textContent = age === null ? "--" : String(age);
ageLabel.textContent = label;
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your full name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateBirthday() {
const value = fields.birthday.value;
if (!value) {
updateAgePreview(null, "Choose date of birth");
setField("birthday", false, "Select your date of birth.");
return false;
}
if (value > todayString) {
updateAgePreview(null, "Future date is not valid");
setField("birthday", false, "Date of birth cannot be in the future.");
return false;
}
const age = calculateAge(value);
if (age < minimumAge) {
updateAgePreview(age, "Minimum age is " + minimumAge);
setField("birthday", false, "You must be at least " + minimumAge + " years old.");
return false;
}
updateAgePreview(age, "Age requirement passed");
setField("birthday", true, "Age requirement passed.");
return true;
}
function validateConfirmAge() {
const messageBox = demo.querySelector("[data-age-message='confirmAge']");
if (!fields.confirmAge.checked) {
messageBox.textContent = "Please confirm that the date of birth is correct.";
messageBox.style.color = "#dc2626";
return false;
}
messageBox.textContent = "Confirmation accepted.";
messageBox.style.color = "#15803d";
return true;
}
function validateAll() {
return validateName() &&
validateEmail() &&
validateBirthday() &&
validateConfirmAge();
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.birthday.addEventListener("change", function () {
validateBirthday();
result.classList.remove("is-visible");
});
fields.confirmAge.addEventListener("change", function () {
validateConfirmAge();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateAgePreview(null, "Choose date of birth");
});
})();
HTML
<div class="js-age-validation-preview" data-js-age-validation-demo>
<div class="js-age-validation-card">
<h3>Verify your age</h3>
<p class="js-age-validation-text">Select your date of birth and confirm that the information is correct before continuing.</p>
<form class="js-age-validation-form" novalidate data-js-age-validation-form>
<div class="js-age-validation-grid">
<div class="js-age-validation-field" data-age-field="name">
<label for="age-validation-name">Full name</label>
<input id="age-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-age-validation-message" data-age-message="name"></div>
</div>
<div class="js-age-validation-field" data-age-field="email">
<label for="age-validation-email">Email address</label>
<input id="age-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-age-validation-message" data-age-message="email"></div>
</div>
<div class="js-age-validation-field is-full" data-age-field="birthday">
<label for="age-validation-birthday">Date of birth</label>
<input id="age-validation-birthday" name="birthday" type="date">
<div class="js-age-validation-message" data-age-message="birthday"></div>
</div>
</div>
<div class="js-age-validation-consent">
<input id="age-validation-confirm" name="confirmAge" type="checkbox">
<label for="age-validation-confirm">I confirm that my date of birth is correct.</label>
<div class="js-age-validation-consent-message" data-age-message="confirmAge"></div>
</div>
<button class="js-age-validation-submit" type="submit">Validate Age</button>
<div class="js-age-validation-result" data-js-age-result>
Age verification validation passed. This form can now continue to the next registration step.
</div>
</form>
</div>
<aside class="js-age-validation-side">
<div>
<div class="js-age-validation-kicker">Age Check</div>
<h3>Calculate and validate age in real time</h3>
<p>The preview updates from the selected date of birth and checks the minimum age requirement.</p>
</div>
<div class="js-age-validation-age-box">
<div>
<span class="js-age-validation-age-number" data-js-age-number>--</span>
<span class="js-age-validation-age-label" data-js-age-label>Choose date of birth</span>
</div>
</div>
<div class="js-age-validation-rules">
<div class="js-age-validation-rule">
<div class="js-age-validation-rule-icon">18+</div>
<div>
<strong>Minimum age</strong>
<span>User must be at least 18 years old.</span>
</div>
</div>
<div class="js-age-validation-rule">
<div class="js-age-validation-rule-icon">✓</div>
<div>
<strong>No future dates</strong>
<span>Future birth dates are rejected.</span>
</div>
</div>
</div>
</aside>
</div>
CSS
.js-age-validation-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 26px;
align-items: stretch;
min-height: 650px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(236, 72, 153, 0.15), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(124, 58, 237, 0.14), transparent 34%),
linear-gradient(135deg, #fdf2f8 0%, #ffffff 52%, #f5f3ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-age-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
display: flex;
flex-direction: column;
justify-content: center;
}
.js-age-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-age-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-age-validation-form {
display: grid;
gap: 16px;
}
.js-age-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-age-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-age-validation-field {
display: grid;
gap: 7px;
}
.js-age-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-age-validation-field input,
.js-age-validation-field select {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-age-validation-field input:focus,
.js-age-validation-field select:focus {
background: #ffffff;
border-color: rgba(236, 72, 153, 0.62);
box-shadow: 0 0 0 4px rgba(236, 72, 153, 0.11);
}
.js-age-validation-field.is-error input,
.js-age-validation-field.is-error select {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-age-validation-field.is-valid input,
.js-age-validation-field.is-valid select {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-age-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-age-validation-field.is-valid .js-age-validation-message {
color: #15803d;
}
.js-age-validation-consent {
display: grid;
grid-template-columns: 22px 1fr;
gap: 12px;
padding: 15px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-age-validation-consent input {
width: 19px;
height: 19px;
margin-top: 2px;
accent-color: #db2777;
}
.js-age-validation-consent label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-age-validation-consent-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-age-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #db2777, #7c3aed);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(219, 39, 119, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-age-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(219, 39, 119, 0.33);
}
.js-age-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-age-validation-result.is-visible {
display: block;
}
.js-age-validation-side {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #831843 0%, #db2777 48%, #6d28d9 100%);
box-shadow: 0 26px 80px rgba(219, 39, 119, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-age-validation-side::before {
content: "";
position: absolute;
right: -120px;
top: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-age-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #fce7f3;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-age-validation-side h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-age-validation-side p {
position: relative;
z-index: 2;
margin: 0;
color: #fce7f3;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-age-validation-age-box {
position: relative;
z-index: 2;
display: grid;
place-items: center;
min-height: 240px;
padding: 24px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
text-align: center;
}
.js-age-validation-age-number {
display: block;
margin-bottom: 10px;
color: #ffffff;
font-size: 82px;
line-height: 0.95;
font-weight: 950;
letter-spacing: -0.08em;
}
.js-age-validation-age-label {
display: block;
color: #fce7f3;
font-size: 14px;
line-height: 1.45;
font-weight: 850;
}
.js-age-validation-rules {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
}
.js-age-validation-rule {
display: grid;
grid-template-columns: 42px 1fr;
gap: 12px;
align-items: center;
padding: 13px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.js-age-validation-rule-icon {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 15px;
background: rgba(255, 255, 255, 0.16);
color: #ffffff;
font-size: 18px;
}
.js-age-validation-rule strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 13px;
line-height: 1.2;
font-weight: 950;
}
.js-age-validation-rule span {
display: block;
color: #fce7f3;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
@media (max-width: 940px) {
.js-age-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-age-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-age-validation-card,
.js-age-validation-side {
padding: 24px;
border-radius: 24px;
}
.js-age-validation-grid {
grid-template-columns: 1fr;
}
.js-age-validation-grid .is-full {
grid-column: auto;
}
.js-age-validation-card h3,
.js-age-validation-side h3 {
font-size: 30px;
}
}
This age verification validation example is useful for registration forms, booking forms, event signups, membership applications, gated content, course enrollment, and any flow where a minimum age requirement must be checked before submission.
20. Message Length Validation
Message length validation helps contact forms, support forms, feedback forms, quote request forms, bug report forms, and application forms collect enough detail. Instead of accepting very short messages, this pattern guides users toward a more useful response.
This example validates a message textarea with minimum and maximum character rules, a live character counter, a visual progress bar, helpful message quality states, and clear success feedback.
- Validates minimum and maximum message length
- Includes live character counter and progress bar
- Shows short, good, and too-long message states
- Validates name, email, topic, and message
- Good for support forms, contact forms, quote forms, and feedback forms
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-message-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-message-validation-form]");
const result = demo.querySelector("[data-js-message-result]");
const counter = demo.querySelector("[data-js-message-counter]");
const progress = demo.querySelector("[data-js-message-progress]");
const qualityScore = demo.querySelector("[data-js-message-quality-score]");
const qualityText = demo.querySelector("[data-js-message-quality-text]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
topic: form.querySelector("[name='topic']"),
message: form.querySelector("[name='message']")
};
const minLength = 80;
const maxLength = 500;
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-message-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-message-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function updateMessageStats() {
const length = fields.message.value.length;
const percent = Math.min(100, Math.round((length / minLength) * 100));
counter.textContent = length + " / " + maxLength;
counter.classList.remove("is-good", "is-bad");
progress.classList.remove("is-short", "is-good", "is-long");
progress.style.width = Math.min(100, Math.round((length / maxLength) * 100)) + "%";
qualityScore.textContent = percent + "%";
if (length === 0) {
qualityText.textContent = "Start typing your message.";
progress.classList.add("is-short");
return;
}
if (length < minLength) {
qualityText.textContent = "Message is still too short.";
counter.classList.add("is-bad");
progress.classList.add("is-short");
return;
}
if (length > maxLength) {
qualityText.textContent = "Message is too long.";
counter.classList.add("is-bad");
progress.classList.add("is-long");
return;
}
qualityText.textContent = "Message length looks good.";
counter.classList.add("is-good");
progress.classList.add("is-good");
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateTopic() {
if (!fields.topic.value) {
setField("topic", false, "Choose a topic.");
return false;
}
setField("topic", true, "Topic selected.");
return true;
}
function validateMessage() {
const length = fields.message.value.trim().length;
updateMessageStats();
if (length < minLength) {
setField("message", false, "Write at least " + minLength + " characters.");
return false;
}
if (length > maxLength) {
setField("message", false, "Keep the message under " + maxLength + " characters.");
return false;
}
setField("message", true, "Message length looks good.");
return true;
}
function validateAll() {
return validateName() &&
validateEmail() &&
validateTopic() &&
validateMessage();
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.topic.addEventListener("change", function () {
validateTopic();
result.classList.remove("is-visible");
});
fields.message.addEventListener("input", function () {
validateMessage();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateMessageStats();
});
})();
HTML
<div class="js-message-validation-preview" data-js-message-validation-demo>
<aside class="js-message-validation-side">
<div>
<div class="js-message-validation-kicker">Message Validation</div>
<h3>Collect useful messages, not empty requests</h3>
<p>Use character rules and live feedback to help users write enough detail before submitting a form.</p>
</div>
<div class="js-message-validation-quality">
<span class="js-message-validation-quality-label">Message quality</span>
<span class="js-message-validation-quality-score" data-js-message-quality-score>0%</span>
<span class="js-message-validation-quality-text" data-js-message-quality-text>Start typing your message.</span>
</div>
<div class="js-message-validation-tips">
<div class="js-message-validation-tip">
<div class="js-message-validation-tip-icon">80</div>
<div>
<strong>Minimum length</strong>
<span>Message must have at least 80 characters.</span>
</div>
</div>
<div class="js-message-validation-tip">
<div class="js-message-validation-tip-icon">500</div>
<div>
<strong>Maximum length</strong>
<span>Message must stay under 500 characters.</span>
</div>
</div>
</div>
</aside>
<div class="js-message-validation-card">
<h3>Contact support</h3>
<p class="js-message-validation-text">Write a clear message with enough detail so support can understand the request.</p>
<form class="js-message-validation-form" novalidate data-js-message-validation-form>
<div class="js-message-validation-grid">
<div class="js-message-validation-field" data-message-field="name">
<label for="message-validation-name">Name</label>
<input id="message-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-message-validation-message" data-message-message="name"></div>
</div>
<div class="js-message-validation-field" data-message-field="email">
<label for="message-validation-email">Email</label>
<input id="message-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-message-validation-message" data-message-message="email"></div>
</div>
<div class="js-message-validation-field is-full" data-message-field="topic">
<label for="message-validation-topic">Topic</label>
<select id="message-validation-topic" name="topic">
<option value="">Choose a topic</option>
<option value="Support">Support</option>
<option value="Quote Request">Quote Request</option>
<option value="Bug Report">Bug Report</option>
<option value="General Question">General Question</option>
</select>
<div class="js-message-validation-message" data-message-message="topic"></div>
</div>
<div class="js-message-validation-field is-full" data-message-field="message">
<label for="message-validation-textarea">Message</label>
<textarea id="message-validation-textarea" name="message" placeholder="Explain the issue, goal, context, or request..."></textarea>
<div class="js-message-validation-counter-row">
<div class="js-message-validation-progress">
<div class="js-message-validation-progress-fill" data-js-message-progress></div>
</div>
<div class="js-message-validation-counter" data-js-message-counter>0 / 500</div>
</div>
<div class="js-message-validation-message" data-message-message="message"></div>
</div>
</div>
<button class="js-message-validation-submit" type="submit">Validate Message</button>
<div class="js-message-validation-result" data-js-message-result>
Message validation passed. This support request can now be sent to your backend or email handler.
</div>
</form>
</div>
</div>
CSS
.js-message-validation-preview {
display: grid;
grid-template-columns: 350px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
min-height: 680px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(14, 165, 233, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(59, 130, 246, 0.14), transparent 34%),
linear-gradient(135deg, #f0f9ff 0%, #ffffff 52%, #eff6ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-message-validation-side {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #0c4a6e 0%, #0284c7 48%, #2563eb 100%);
box-shadow: 0 26px 80px rgba(2, 132, 199, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-message-validation-side::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-message-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #dbeafe;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-message-validation-side h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-message-validation-side p {
position: relative;
z-index: 2;
margin: 0;
color: #dbeafe;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-message-validation-quality {
position: relative;
z-index: 2;
padding: 22px;
border-radius: 26px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
}
.js-message-validation-quality-label {
display: block;
margin-bottom: 10px;
color: #dbeafe;
font-size: 11px;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-message-validation-quality-score {
display: block;
margin-bottom: 8px;
color: #ffffff;
font-size: 46px;
line-height: 1;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-message-validation-quality-text {
display: block;
color: #dbeafe;
font-size: 14px;
line-height: 1.45;
font-weight: 750;
}
.js-message-validation-tips {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
}
.js-message-validation-tip {
display: grid;
grid-template-columns: 42px 1fr;
gap: 12px;
align-items: center;
padding: 13px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.js-message-validation-tip-icon {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 15px;
background: rgba(255, 255, 255, 0.16);
color: #ffffff;
font-size: 18px;
}
.js-message-validation-tip strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 13px;
line-height: 1.2;
font-weight: 950;
}
.js-message-validation-tip span {
display: block;
color: #dbeafe;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
.js-message-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-message-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-message-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-message-validation-form {
display: grid;
gap: 16px;
}
.js-message-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-message-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-message-validation-field {
display: grid;
gap: 7px;
}
.js-message-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-message-validation-field input,
.js-message-validation-field select,
.js-message-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-message-validation-field textarea {
min-height: 170px;
resize: vertical;
}
.js-message-validation-field input:focus,
.js-message-validation-field select:focus,
.js-message-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(2, 132, 199, 0.62);
box-shadow: 0 0 0 4px rgba(2, 132, 199, 0.11);
}
.js-message-validation-field.is-error input,
.js-message-validation-field.is-error select,
.js-message-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-message-validation-field.is-valid input,
.js-message-validation-field.is-valid select,
.js-message-validation-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-message-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-message-validation-field.is-valid .js-message-validation-message {
color: #15803d;
}
.js-message-validation-counter-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-top: -2px;
}
.js-message-validation-counter {
color: #64748b;
font-size: 12px;
font-weight: 900;
}
.js-message-validation-counter.is-good {
color: #15803d;
}
.js-message-validation-counter.is-bad {
color: #dc2626;
}
.js-message-validation-progress {
flex: 1;
height: 9px;
overflow: hidden;
border-radius: 999px;
background: #e2e8f0;
}
.js-message-validation-progress-fill {
width: 0%;
height: 100%;
border-radius: 999px;
background: linear-gradient(135deg, #0284c7, #2563eb);
transition: width 0.2s ease, background 0.2s ease;
}
.js-message-validation-progress-fill.is-short {
background: linear-gradient(135deg, #ef4444, #f97316);
}
.js-message-validation-progress-fill.is-good {
background: linear-gradient(135deg, #22c55e, #0284c7);
}
.js-message-validation-progress-fill.is-long {
background: linear-gradient(135deg, #ef4444, #be123c);
}
.js-message-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #0284c7, #2563eb);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(2, 132, 199, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-message-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(2, 132, 199, 0.33);
}
.js-message-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-message-validation-result.is-visible {
display: block;
}
@media (max-width: 940px) {
.js-message-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-message-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-message-validation-side,
.js-message-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-message-validation-grid {
grid-template-columns: 1fr;
}
.js-message-validation-grid .is-full {
grid-column: auto;
}
.js-message-validation-card h3,
.js-message-validation-side h3 {
font-size: 30px;
}
.js-message-validation-counter-row {
align-items: stretch;
flex-direction: column;
}
}
This message length validation example is useful for contact forms, quote request forms, support tickets, bug reports, feedback forms, application forms, onboarding questionnaires, and any form where the message needs enough context to be useful.
21. Multi-Step Form Validation
Multi-step form validation helps longer forms feel easier by validating each step before the user moves forward. This pattern is useful for onboarding forms, quote request forms, checkout flows, booking forms, registration forms, and application forms.
This example validates each step separately: personal details, project details, and final confirmation. Users cannot move to the next step until the current step is complete, and the progress indicator updates as the form moves forward.
- Validates one step at a time
- Blocks next step until required fields are valid
- Includes progress indicator and step summary
- Validates name, email, project type, budget, timeline, and confirmation
- Good for onboarding, quote forms, applications, bookings, and checkout flows
Project request
Complete each step. The next button validates the current step before continuing.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-multistep-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-multistep-validation-form]");
const panels = Array.from(demo.querySelectorAll("[data-js-panel]"));
const dots = Array.from(demo.querySelectorAll("[data-js-step-dot]"));
const progress = demo.querySelector("[data-js-multistep-progress]");
const progressTitle = demo.querySelector("[data-js-multistep-title]");
const progressPercent = demo.querySelector("[data-js-multistep-percent]");
const prevButton = demo.querySelector("[data-js-multistep-prev]");
const nextButton = demo.querySelector("[data-js-multistep-next]");
const submitButton = demo.querySelector("[data-js-multistep-submit]");
const result = demo.querySelector("[data-js-multistep-result]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
project: form.querySelector("[name='project']"),
budget: form.querySelector("[name='budget']"),
timeline: form.querySelector("[name='timeline']"),
message: form.querySelector("[name='message']"),
confirm: form.querySelector("[name='confirm']")
};
let currentStep = 0;
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-multistep-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-multistep-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your full name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateProject() {
if (!fields.project.value) {
setField("project", false, "Choose a project type.");
return false;
}
setField("project", true, "Project type selected.");
return true;
}
function validateBudget() {
if (!fields.budget.value) {
setField("budget", false, "Choose a budget range.");
return false;
}
setField("budget", true, "Budget selected.");
return true;
}
function validateTimeline() {
if (!fields.timeline.value) {
setField("timeline", false, "Choose a timeline.");
return false;
}
setField("timeline", true, "Timeline selected.");
return true;
}
function validateMessage() {
if (fields.message.value.trim().length < 20) {
setField("message", false, "Write at least 20 characters.");
return false;
}
setField("message", true, "Message looks detailed enough.");
return true;
}
function validateConfirm() {
const messageBox = demo.querySelector("[data-multistep-message='confirm']");
if (!fields.confirm.checked) {
messageBox.textContent = "Please confirm before submitting.";
messageBox.style.color = "#dc2626";
return false;
}
messageBox.textContent = "Confirmation accepted.";
messageBox.style.color = "#15803d";
return true;
}
function validateStep(step) {
if (step === 0) {
return validateName() && validateEmail();
}
if (step === 1) {
return validateProject() && validateBudget() && validateTimeline() && validateMessage();
}
if (step === 2) {
return validateConfirm();
}
return false;
}
function updateStep() {
panels.forEach(function (panel, index) {
panel.classList.toggle("is-active", index === currentStep);
});
dots.forEach(function (dot, index) {
dot.classList.toggle("is-active", index === currentStep);
dot.classList.toggle("is-done", index < currentStep);
});
const percent = Math.round(((currentStep + 1) / panels.length) * 100);
progress.style.width = percent + "%";
progressTitle.textContent = "Step " + (currentStep + 1) + " of " + panels.length;
progressPercent.textContent = percent + "%";
prevButton.hidden = currentStep === 0;
nextButton.hidden = currentStep === panels.length - 1;
submitButton.hidden = currentStep !== panels.length - 1;
result.classList.remove("is-visible");
}
nextButton.addEventListener("click", function () {
if (validateStep(currentStep)) {
currentStep++;
updateStep();
}
});
prevButton.addEventListener("click", function () {
currentStep--;
updateStep();
});
fields.name.addEventListener("input", validateName);
fields.email.addEventListener("input", validateEmail);
fields.project.addEventListener("change", validateProject);
fields.budget.addEventListener("change", validateBudget);
fields.timeline.addEventListener("change", validateTimeline);
fields.message.addEventListener("input", validateMessage);
fields.confirm.addEventListener("change", validateConfirm);
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateStep(2)) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateStep();
});
})();
HTML
<div class="js-multistep-validation-preview" data-js-multistep-validation-demo>
<aside class="js-multistep-validation-sidebar">
<div>
<div class="js-multistep-validation-kicker">Multi-Step Validation</div>
<h3>Validate every step before users continue</h3>
<p>Break long forms into smaller steps and validate each section before moving forward.</p>
</div>
<div class="js-multistep-validation-progress-box">
<div class="js-multistep-validation-progress-head">
<strong data-js-multistep-title>Step 1 of 3</strong>
<span data-js-multistep-percent>33%</span>
</div>
<div class="js-multistep-validation-progress-track">
<div class="js-multistep-validation-progress-fill" data-js-multistep-progress></div>
</div>
</div>
<div class="js-multistep-validation-steps-list">
<div class="js-multistep-validation-step-item is-active" data-js-step-dot="0">
<div class="js-multistep-validation-step-number">1</div>
<div>
<strong>Contact details</strong>
<span>Name and email validation.</span>
</div>
</div>
<div class="js-multistep-validation-step-item" data-js-step-dot="1">
<div class="js-multistep-validation-step-number">2</div>
<div>
<strong>Project details</strong>
<span>Budget, timeline, and message.</span>
</div>
</div>
<div class="js-multistep-validation-step-item" data-js-step-dot="2">
<div class="js-multistep-validation-step-number">3</div>
<div>
<strong>Confirmation</strong>
<span>Final review and consent.</span>
</div>
</div>
</div>
</aside>
<div class="js-multistep-validation-card">
<h3>Project request</h3>
<p class="js-multistep-validation-text">Complete each step. The next button validates the current step before continuing.</p>
<form class="js-multistep-validation-form" novalidate data-js-multistep-validation-form>
<div class="js-multistep-validation-panel is-active" data-js-panel="0">
<div class="js-multistep-validation-grid">
<div class="js-multistep-validation-field" data-multistep-field="name">
<label for="multistep-validation-name">Full name</label>
<input id="multistep-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-multistep-validation-message" data-multistep-message="name"></div>
</div>
<div class="js-multistep-validation-field" data-multistep-field="email">
<label for="multistep-validation-email">Email address</label>
<input id="multistep-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-multistep-validation-message" data-multistep-message="email"></div>
</div>
</div>
</div>
<div class="js-multistep-validation-panel" data-js-panel="1">
<div class="js-multistep-validation-grid">
<div class="js-multistep-validation-field" data-multistep-field="project">
<label for="multistep-validation-project">Project type</label>
<select id="multistep-validation-project" name="project">
<option value="">Choose project type</option>
<option value="Website">Website</option>
<option value="Plugin">Plugin</option>
<option value="Landing Page">Landing Page</option>
<option value="AI Tool">AI Tool</option>
</select>
<div class="js-multistep-validation-message" data-multistep-message="project"></div>
</div>
<div class="js-multistep-validation-field" data-multistep-field="budget">
<label for="multistep-validation-budget">Budget</label>
<select id="multistep-validation-budget" name="budget">
<option value="">Choose budget</option>
<option value="Under $500">Under $500</option>
<option value="$500-$1500">$500-$1500</option>
<option value="$1500-$5000">$1500-$5000</option>
<option value="$5000+">$5000+</option>
</select>
<div class="js-multistep-validation-message" data-multistep-message="budget"></div>
</div>
<div class="js-multistep-validation-field" data-multistep-field="timeline">
<label for="multistep-validation-timeline">Timeline</label>
<select id="multistep-validation-timeline" name="timeline">
<option value="">Choose timeline</option>
<option value="ASAP">ASAP</option>
<option value="2-4 weeks">2-4 weeks</option>
<option value="1-2 months">1-2 months</option>
<option value="Flexible">Flexible</option>
</select>
<div class="js-multistep-validation-message" data-multistep-message="timeline"></div>
</div>
<div class="js-multistep-validation-field is-full" data-multistep-field="message">
<label for="multistep-validation-message">Project message</label>
<textarea id="multistep-validation-message" name="message" placeholder="Briefly describe what you need..."></textarea>
<div class="js-multistep-validation-message" data-multistep-message="message"></div>
</div>
</div>
</div>
<div class="js-multistep-validation-panel" data-js-panel="2">
<div class="js-multistep-validation-confirm">
<input id="multistep-validation-confirm" name="confirm" type="checkbox">
<label for="multistep-validation-confirm">I confirm that the information is correct and I want to submit this project request.</label>
<div class="js-multistep-validation-confirm-message" data-multistep-message="confirm"></div>
</div>
</div>
<div class="js-multistep-validation-actions">
<button class="js-multistep-validation-prev" type="button" data-js-multistep-prev hidden>Back</button>
<button class="js-multistep-validation-next" type="button" data-js-multistep-next>Next Step</button>
<button class="js-multistep-validation-submit" type="submit" data-js-multistep-submit hidden>Submit Request</button>
</div>
<div class="js-multistep-validation-result" data-js-multistep-result>
Multi-step validation passed. This project request can now be sent to your backend or email handler.
</div>
</form>
</div>
</div>
CSS
.js-multistep-validation-preview {
display: grid;
grid-template-columns: 350px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
min-height: 690px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(59, 130, 246, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(20, 184, 166, 0.14), transparent 34%),
linear-gradient(135deg, #eff6ff 0%, #ffffff 52%, #f0fdfa 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-multistep-validation-sidebar {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255,255,255,0.20), transparent 34%),
linear-gradient(135deg, #172554 0%, #2563eb 48%, #0f766e 100%);
box-shadow: 0 26px 80px rgba(37, 99, 235, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-multistep-validation-sidebar::before {
content: "";
position: absolute;
right: -120px;
top: -120px;
width: 290px;
height: 290px;
border-radius: 999px;
background: rgba(255,255,255,0.12);
}
.js-multistep-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255,255,255,0.14);
border: 1px solid rgba(255,255,255,0.18);
color: #dbeafe;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-multistep-validation-sidebar h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-multistep-validation-sidebar p {
position: relative;
z-index: 2;
margin: 0;
color: #dbeafe;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-multistep-validation-progress-box {
position: relative;
z-index: 2;
display: grid;
gap: 14px;
padding: 18px;
border-radius: 24px;
background: rgba(255,255,255,0.13);
border: 1px solid rgba(255,255,255,0.15);
}
.js-multistep-validation-progress-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.js-multistep-validation-progress-head strong {
color: #ffffff;
font-size: 15px;
line-height: 1.2;
font-weight: 950;
}
.js-multistep-validation-progress-head span {
color: #ccfbf1;
font-size: 13px;
font-weight: 900;
}
.js-multistep-validation-progress-track {
height: 10px;
overflow: hidden;
border-radius: 999px;
background: rgba(255,255,255,0.18);
}
.js-multistep-validation-progress-fill {
width: 33.333%;
height: 100%;
border-radius: 999px;
background: #ffffff;
transition: width 0.25s ease;
}
.js-multistep-validation-steps-list {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
}
.js-multistep-validation-step-item {
display: grid;
grid-template-columns: 42px 1fr;
gap: 12px;
align-items: center;
padding: 13px;
border-radius: 18px;
background: rgba(255,255,255,0.13);
border: 1px solid rgba(255,255,255,0.14);
}
.js-multistep-validation-step-number {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 15px;
background: rgba(255,255,255,0.16);
color: #ffffff;
font-size: 15px;
font-weight: 950;
}
.js-multistep-validation-step-item.is-active .js-multistep-validation-step-number {
background: #ffffff;
color: #2563eb;
}
.js-multistep-validation-step-item.is-done .js-multistep-validation-step-number {
background: #dcfce7;
color: #15803d;
}
.js-multistep-validation-step-item strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 13px;
line-height: 1.2;
font-weight: 950;
}
.js-multistep-validation-step-item span {
display: block;
color: #dbeafe;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
.js-multistep-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-multistep-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-multistep-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-multistep-validation-form {
display: grid;
gap: 18px;
}
.js-multistep-validation-panel {
display: none;
gap: 16px;
}
.js-multistep-validation-panel.is-active {
display: grid;
}
.js-multistep-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-multistep-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-multistep-validation-field {
display: grid;
gap: 7px;
}
.js-multistep-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-multistep-validation-field input,
.js-multistep-validation-field select,
.js-multistep-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-multistep-validation-field textarea {
min-height: 120px;
resize: vertical;
}
.js-multistep-validation-field input:focus,
.js-multistep-validation-field select:focus,
.js-multistep-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(37, 99, 235, 0.62);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.11);
}
.js-multistep-validation-field.is-error input,
.js-multistep-validation-field.is-error select,
.js-multistep-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-multistep-validation-field.is-valid input,
.js-multistep-validation-field.is-valid select,
.js-multistep-validation-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-multistep-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-multistep-validation-field.is-valid .js-multistep-validation-message {
color: #15803d;
}
.js-multistep-validation-confirm {
display: grid;
grid-template-columns: 22px 1fr;
gap: 12px;
padding: 15px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-multistep-validation-confirm input {
width: 19px;
height: 19px;
margin-top: 2px;
accent-color: #2563eb;
}
.js-multistep-validation-confirm label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-multistep-validation-confirm-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-multistep-validation-actions {
display: flex;
justify-content: space-between;
gap: 12px;
padding-top: 4px;
}
.js-multistep-validation-prev,
.js-multistep-validation-next,
.js-multistep-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 52px;
padding: 15px 20px;
border: 0;
border-radius: 999px;
font-size: 14px;
line-height: 1;
font-weight: 950;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease, opacity 0.2s ease;
}
.js-multistep-validation-prev {
background: #e2e8f0;
color: #334155;
}
.js-multistep-validation-next,
.js-multistep-validation-submit {
background: linear-gradient(135deg, #2563eb, #0f766e);
color: #ffffff;
box-shadow: 0 18px 42px rgba(37, 99, 235, 0.24);
}
.js-multistep-validation-prev:hover,
.js-multistep-validation-next:hover,
.js-multistep-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
}
.js-multistep-validation-prev[hidden],
.js-multistep-validation-next[hidden],
.js-multistep-validation-submit[hidden] {
display: none;
}
.js-multistep-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-multistep-validation-result.is-visible {
display: block;
}
@media (max-width: 940px) {
.js-multistep-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-multistep-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-multistep-validation-sidebar,
.js-multistep-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-multistep-validation-grid {
grid-template-columns: 1fr;
}
.js-multistep-validation-grid .is-full {
grid-column: auto;
}
.js-multistep-validation-actions {
flex-direction: column;
}
.js-multistep-validation-card h3,
.js-multistep-validation-sidebar h3 {
font-size: 30px;
}
}
This multi-step form validation example is useful for onboarding forms, quote request flows, checkout steps, registration forms, event applications, booking forms, and longer forms where users should complete one section at a time.
22. Newsletter Preferences Validation
Newsletter preferences validation helps email signup forms collect a valid email address and at least one subscription topic before submission. This is better than a plain email field because users can choose what type of content they want to receive.
This example validates email, frequency, topic selection, and marketing consent. It also includes selectable preference cards and a live summary preview for a more polished newsletter signup experience.
- Validates email address and subscription frequency
- Requires at least one newsletter topic
- Includes required consent checkbox validation
- Shows selected preferences in a live summary
- Good for newsletter forms, content hubs, SaaS blogs, ecommerce email lists, and creator websites
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-newsletter-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-newsletter-validation-form]");
const result = demo.querySelector("[data-js-newsletter-result]");
const fields = {
email: form.querySelector("[name='email']"),
frequency: form.querySelector("[name='frequency']"),
consent: form.querySelector("[name='consent']")
};
const topics = Array.from(form.querySelectorAll("[name='topics']"));
const preview = {
email: demo.querySelector("[data-js-newsletter-preview-email]"),
frequency: demo.querySelector("[data-js-newsletter-preview-frequency]"),
topics: demo.querySelector("[data-js-newsletter-preview-topics]")
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-newsletter-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-newsletter-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
}
function getSelectedTopics() {
return topics
.filter(function (topic) {
return topic.checked;
})
.map(function (topic) {
return topic.value;
});
}
function updatePreview() {
const selectedTopics = getSelectedTopics();
preview.email.textContent = fields.email.value.trim() || "Not entered yet";
preview.frequency.textContent = fields.frequency.value || "Not selected yet";
preview.topics.textContent = selectedTopics.length ? selectedTopics.join(", ") : "No topics selected";
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateFrequency() {
if (!fields.frequency.value) {
setField("frequency", false, "Choose how often you want to receive emails.");
return false;
}
setField("frequency", true, "Frequency selected.");
return true;
}
function validateTopics() {
const messageBox = demo.querySelector("[data-newsletter-message='topics']");
const selectedTopics = getSelectedTopics();
if (!selectedTopics.length) {
messageBox.textContent = "Choose at least one newsletter topic.";
messageBox.classList.remove("is-valid");
return false;
}
messageBox.textContent = selectedTopics.length + " topic" + (selectedTopics.length > 1 ? "s" : "") + " selected.";
messageBox.classList.add("is-valid");
return true;
}
function validateConsent() {
const messageBox = demo.querySelector("[data-newsletter-message='consent']");
if (!fields.consent.checked) {
messageBox.textContent = "Please accept newsletter consent before subscribing.";
messageBox.style.color = "#dc2626";
return false;
}
messageBox.textContent = "Consent accepted.";
messageBox.style.color = "#15803d";
return true;
}
function validateAll() {
return validateEmail() &&
validateFrequency() &&
validateTopics() &&
validateConsent();
}
fields.email.addEventListener("input", function () {
validateEmail();
updatePreview();
result.classList.remove("is-visible");
});
fields.frequency.addEventListener("change", function () {
validateFrequency();
updatePreview();
result.classList.remove("is-visible");
});
fields.consent.addEventListener("change", function () {
validateConsent();
result.classList.remove("is-visible");
});
topics.forEach(function (topic) {
topic.addEventListener("change", function () {
validateTopics();
updatePreview();
result.classList.remove("is-visible");
});
});
form.addEventListener("submit", function (event) {
event.preventDefault();
updatePreview();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updatePreview();
});
})();
HTML
<div class="js-newsletter-validation-preview" data-js-newsletter-validation-demo>
<div class="js-newsletter-validation-card">
<h3>Newsletter signup</h3>
<p class="js-newsletter-validation-text">Choose topics, frequency, and consent before subscribing.</p>
<form class="js-newsletter-validation-form" novalidate data-js-newsletter-validation-form>
<div class="js-newsletter-validation-field" data-newsletter-field="email">
<label for="newsletter-validation-email">Email address</label>
<input id="newsletter-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-newsletter-validation-message" data-newsletter-message="email"></div>
</div>
<div class="js-newsletter-validation-field" data-newsletter-field="frequency">
<label for="newsletter-validation-frequency">Email frequency</label>
<select id="newsletter-validation-frequency" name="frequency">
<option value="">Choose frequency</option>
<option value="Weekly">Weekly</option>
<option value="Twice per month">Twice per month</option>
<option value="Monthly">Monthly</option>
</select>
<div class="js-newsletter-validation-message" data-newsletter-message="frequency"></div>
</div>
<div class="js-newsletter-validation-topics">
<div class="js-newsletter-validation-topic-title">Newsletter topics</div>
<div class="js-newsletter-validation-topic-grid">
<label class="js-newsletter-validation-topic">
<input type="checkbox" name="topics" value="JavaScript examples">
<span><strong>JavaScript</strong>Forms, UI patterns, and interactive examples.</span>
</label>
<label class="js-newsletter-validation-topic">
<input type="checkbox" name="topics" value="CSS layouts">
<span><strong>CSS Layouts</strong>Modern layout ideas and frontend sections.</span>
</label>
<label class="js-newsletter-validation-topic">
<input type="checkbox" name="topics" value="WordPress tips">
<span><strong>WordPress</strong>Plugins, performance, SEO, and Gutenberg.</span>
</label>
<label class="js-newsletter-validation-topic">
<input type="checkbox" name="topics" value="AI tools">
<span><strong>AI Tools</strong>Automation, prompts, plugins, and workflows.</span>
</label>
</div>
<div class="js-newsletter-validation-topic-message" data-newsletter-message="topics">Choose at least one topic.</div>
</div>
<div class="js-newsletter-validation-consent">
<input id="newsletter-validation-consent" name="consent" type="checkbox">
<label for="newsletter-validation-consent">I agree to receive selected newsletter emails and understand I can unsubscribe anytime.</label>
<div class="js-newsletter-validation-consent-message" data-newsletter-message="consent"></div>
</div>
<button class="js-newsletter-validation-submit" type="submit">Validate Signup</button>
<div class="js-newsletter-validation-result" data-js-newsletter-result>
Newsletter validation passed. This signup can now be sent to your email marketing platform.
</div>
</form>
</div>
<aside class="js-newsletter-validation-summary">
<div>
<div class="js-newsletter-validation-kicker">Live Preferences</div>
<h3>Personalized signup summary</h3>
<p>Show users what they selected before sending the subscription request.</p>
</div>
<div class="js-newsletter-validation-summary-box">
<div class="js-newsletter-validation-summary-line">
<span>Email</span>
<strong data-js-newsletter-preview-email>Not entered yet</strong>
</div>
<div class="js-newsletter-validation-summary-line">
<span>Frequency</span>
<strong data-js-newsletter-preview-frequency>Not selected yet</strong>
</div>
<div class="js-newsletter-validation-summary-line">
<span>Topics</span>
<strong data-js-newsletter-preview-topics>No topics selected</strong>
</div>
</div>
<div class="js-newsletter-validation-mini-cards">
<div class="js-newsletter-validation-mini-card">
<div class="js-newsletter-validation-mini-icon">✉</div>
<div>
<strong>Better targeting</strong>
<span>Users choose the content they want.</span>
</div>
</div>
<div class="js-newsletter-validation-mini-card">
<div class="js-newsletter-validation-mini-icon">✓</div>
<div>
<strong>Clear consent</strong>
<span>Required checkbox prevents accidental signup.</span>
</div>
</div>
</div>
</aside>
</div>
CSS
.js-newsletter-validation-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 26px;
align-items: stretch;
min-height: 680px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(168, 85, 247, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(236, 72, 153, 0.14), transparent 34%),
linear-gradient(135deg, #faf5ff 0%, #ffffff 52%, #fdf2f8 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-newsletter-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-newsletter-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-newsletter-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-newsletter-validation-form {
display: grid;
gap: 16px;
}
.js-newsletter-validation-field {
display: grid;
gap: 7px;
}
.js-newsletter-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-newsletter-validation-field input[type="email"],
.js-newsletter-validation-field select {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-newsletter-validation-field input[type="email"]:focus,
.js-newsletter-validation-field select:focus {
background: #ffffff;
border-color: rgba(168, 85, 247, 0.62);
box-shadow: 0 0 0 4px rgba(168, 85, 247, 0.11);
}
.js-newsletter-validation-field.is-error input[type="email"],
.js-newsletter-validation-field.is-error select {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-newsletter-validation-field.is-valid input[type="email"],
.js-newsletter-validation-field.is-valid select {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-newsletter-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-newsletter-validation-field.is-valid .js-newsletter-validation-message {
color: #15803d;
}
.js-newsletter-validation-topics {
display: grid;
gap: 9px;
}
.js-newsletter-validation-topic-title {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-newsletter-validation-topic-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.js-newsletter-validation-topic {
position: relative;
}
.js-newsletter-validation-topic input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.js-newsletter-validation-topic span {
display: block;
min-height: 78px;
padding: 14px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.10);
color: #334155;
font-size: 13px;
line-height: 1.45;
font-weight: 800;
cursor: pointer;
transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}
.js-newsletter-validation-topic strong {
display: block;
margin-bottom: 5px;
color: #111827;
font-size: 14px;
line-height: 1.2;
font-weight: 950;
}
.js-newsletter-validation-topic input:checked + span {
background: #faf5ff;
border-color: rgba(168, 85, 247, 0.48);
box-shadow: 0 14px 30px rgba(168, 85, 247, 0.12);
transform: translateY(-1px);
}
.js-newsletter-validation-topic-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-newsletter-validation-topic-message.is-valid {
color: #15803d;
}
.js-newsletter-validation-consent {
display: grid;
grid-template-columns: 22px 1fr;
gap: 12px;
padding: 15px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-newsletter-validation-consent input {
width: 19px;
height: 19px;
margin-top: 2px;
accent-color: #a855f7;
}
.js-newsletter-validation-consent label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-newsletter-validation-consent-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-newsletter-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #a855f7, #ec4899);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(168, 85, 247, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-newsletter-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(168, 85, 247, 0.33);
}
.js-newsletter-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-newsletter-validation-result.is-visible {
display: block;
}
.js-newsletter-validation-summary {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255,255,255,0.20), transparent 34%),
linear-gradient(135deg, #581c87 0%, #a855f7 48%, #db2777 100%);
box-shadow: 0 26px 80px rgba(168, 85, 247, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-newsletter-validation-summary::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255,255,255,0.12);
}
.js-newsletter-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255,255,255,0.14);
border: 1px solid rgba(255,255,255,0.18);
color: #f3e8ff;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-newsletter-validation-summary h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-newsletter-validation-summary p {
position: relative;
z-index: 2;
margin: 0;
color: #f3e8ff;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-newsletter-validation-summary-box {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
padding: 18px;
border-radius: 24px;
background: rgba(255,255,255,0.13);
border: 1px solid rgba(255,255,255,0.15);
}
.js-newsletter-validation-summary-line {
display: grid;
gap: 4px;
}
.js-newsletter-validation-summary-line span {
color: #f3e8ff;
font-size: 11px;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-newsletter-validation-summary-line strong {
overflow-wrap: anywhere;
color: #ffffff;
font-size: 15px;
line-height: 1.35;
font-weight: 900;
}
.js-newsletter-validation-mini-cards {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
}
.js-newsletter-validation-mini-card {
display: grid;
grid-template-columns: 42px 1fr;
gap: 12px;
align-items: center;
padding: 13px;
border-radius: 18px;
background: rgba(255,255,255,0.13);
border: 1px solid rgba(255,255,255,0.14);
}
.js-newsletter-validation-mini-icon {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 15px;
background: rgba(255,255,255,0.16);
color: #ffffff;
font-size: 18px;
}
.js-newsletter-validation-mini-card strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 13px;
line-height: 1.2;
font-weight: 950;
}
.js-newsletter-validation-mini-card span {
display: block;
color: #f3e8ff;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
@media (max-width: 940px) {
.js-newsletter-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-newsletter-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-newsletter-validation-card,
.js-newsletter-validation-summary {
padding: 24px;
border-radius: 24px;
}
.js-newsletter-validation-topic-grid {
grid-template-columns: 1fr;
}
.js-newsletter-validation-card h3,
.js-newsletter-validation-summary h3 {
font-size: 30px;
}
}
This newsletter preferences validation example is useful for newsletter signup forms, SaaS blog subscriptions, ecommerce email lists, creator newsletters, content hubs, lead magnets, and resource library access forms.
23. Survey Form Validation
Survey form validation helps feedback forms, customer satisfaction surveys, product research forms, onboarding questionnaires, and rating forms collect complete answers before submission. A good survey form should validate required radio groups, checkbox groups, text fields, and optional comments clearly.
This example validates a satisfaction rating, required feature checkboxes, recommendation choice, email address, and feedback text. It also updates a live survey summary so users can see what they have selected before submitting.
- Validates required radio button groups
- Requires at least two selected feature checkboxes
- Validates email and feedback message
- Includes live survey summary preview
- Good for customer surveys, feedback forms, onboarding questionnaires, and product research
Customer survey
Answer the required survey questions before submitting feedback.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-survey-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-survey-validation-form]");
const result = demo.querySelector("[data-js-survey-result]");
const email = form.querySelector("[name='email']");
const feedback = form.querySelector("[name='feedback']");
const ratings = Array.from(form.querySelectorAll("[name='rating']"));
const features = Array.from(form.querySelectorAll("[name='features']"));
const recommendOptions = Array.from(form.querySelectorAll("[name='recommend']"));
const preview = {
email: demo.querySelector("[data-js-survey-preview-email]"),
rating: demo.querySelector("[data-js-survey-preview-rating]"),
features: demo.querySelector("[data-js-survey-preview-features]"),
recommend: demo.querySelector("[data-js-survey-preview-recommend]"),
featureCount: demo.querySelector("[data-js-survey-stat-count]"),
ratingNumber: demo.querySelector("[data-js-survey-stat-rating]")
};
function getCheckedValue(items) {
const selected = items.find(function (item) {
return item.checked;
});
return selected ? selected.value : "";
}
function getCheckedValues(items) {
return items
.filter(function (item) {
return item.checked;
})
.map(function (item) {
return item.value;
});
}
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-survey-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-survey-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
}
function setGroup(groupName, isValid, message) {
const messageBox = demo.querySelector("[data-survey-message='" + groupName + "']");
messageBox.textContent = message;
messageBox.classList.toggle("is-valid", isValid);
}
function updatePreview() {
const rating = getCheckedValue(ratings);
const selectedFeatures = getCheckedValues(features);
const recommend = getCheckedValue(recommendOptions);
preview.email.textContent = email.value.trim() || "Not entered yet";
preview.rating.textContent = rating ? rating + " out of 5" : "No rating selected";
preview.features.textContent = selectedFeatures.length ? selectedFeatures.join(", ") : "No features selected";
preview.recommend.textContent = recommend || "No answer selected";
preview.featureCount.textContent = String(selectedFeatures.length);
preview.ratingNumber.textContent = rating || "--";
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateRating() {
const rating = getCheckedValue(ratings);
if (!rating) {
setGroup("rating", false, "Choose one satisfaction rating.");
return false;
}
setGroup("rating", true, "Rating selected.");
return true;
}
function validateFeatures() {
const selectedFeatures = getCheckedValues(features);
if (selectedFeatures.length < 2) {
setGroup("features", false, "Choose at least two features.");
return false;
}
setGroup("features", true, selectedFeatures.length + " features selected.");
return true;
}
function validateRecommend() {
const recommend = getCheckedValue(recommendOptions);
if (!recommend) {
setGroup("recommend", false, "Choose one recommendation answer.");
return false;
}
setGroup("recommend", true, "Recommendation answer selected.");
return true;
}
function validateFeedback() {
if (feedback.value.trim().length < 20) {
setField("feedback", false, "Write at least 20 characters of feedback.");
return false;
}
setField("feedback", true, "Feedback looks detailed enough.");
return true;
}
function validateAll() {
return validateEmail() &&
validateRating() &&
validateFeatures() &&
validateRecommend() &&
validateFeedback();
}
email.addEventListener("input", function () {
validateEmail();
updatePreview();
result.classList.remove("is-visible");
});
feedback.addEventListener("input", function () {
validateFeedback();
result.classList.remove("is-visible");
});
ratings.forEach(function (item) {
item.addEventListener("change", function () {
validateRating();
updatePreview();
result.classList.remove("is-visible");
});
});
features.forEach(function (item) {
item.addEventListener("change", function () {
validateFeatures();
updatePreview();
result.classList.remove("is-visible");
});
});
recommendOptions.forEach(function (item) {
item.addEventListener("change", function () {
validateRecommend();
updatePreview();
result.classList.remove("is-visible");
});
});
form.addEventListener("submit", function (event) {
event.preventDefault();
updatePreview();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updatePreview();
});
})();
HTML
<div class="js-survey-validation-preview" data-js-survey-validation-demo>
<div class="js-survey-validation-card">
<h3>Customer survey</h3>
<p class="js-survey-validation-text">Answer the required survey questions before submitting feedback.</p>
<form class="js-survey-validation-form" novalidate data-js-survey-validation-form>
<div class="js-survey-validation-field" data-survey-field="email">
<label for="survey-validation-email">Email address</label>
<input id="survey-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-survey-validation-message" data-survey-message="email"></div>
</div>
<div class="js-survey-validation-choice-group">
<div class="js-survey-validation-group-title">How satisfied are you?</div>
<div class="js-survey-validation-rating-grid">
<label class="js-survey-validation-radio-card">
<input type="radio" name="rating" value="1">
<span>1</span>
</label>
<label class="js-survey-validation-radio-card">
<input type="radio" name="rating" value="2">
<span>2</span>
</label>
<label class="js-survey-validation-radio-card">
<input type="radio" name="rating" value="3">
<span>3</span>
</label>
<label class="js-survey-validation-radio-card">
<input type="radio" name="rating" value="4">
<span>4</span>
</label>
<label class="js-survey-validation-radio-card">
<input type="radio" name="rating" value="5">
<span>5</span>
</label>
</div>
<div class="js-survey-validation-group-message" data-survey-message="rating">Choose one satisfaction rating.</div>
</div>
<div class="js-survey-validation-choice-group">
<div class="js-survey-validation-group-title">Which features matter most? Choose at least 2.</div>
<div class="js-survey-validation-feature-grid">
<label class="js-survey-validation-checkbox-card">
<input type="checkbox" name="features" value="Design quality">
<span><strong>Design quality</strong><small>Modern visual style and layout.</small></span>
</label>
<label class="js-survey-validation-checkbox-card">
<input type="checkbox" name="features" value="Page speed">
<span><strong>Page speed</strong><small>Fast loading and smooth UX.</small></span>
</label>
<label class="js-survey-validation-checkbox-card">
<input type="checkbox" name="features" value="Easy editing">
<span><strong>Easy editing</strong><small>Simple content management.</small></span>
</label>
<label class="js-survey-validation-checkbox-card">
<input type="checkbox" name="features" value="SEO structure">
<span><strong>SEO structure</strong><small>Better ranking potential.</small></span>
</label>
</div>
<div class="js-survey-validation-group-message" data-survey-message="features">Choose at least two features.</div>
</div>
<div class="js-survey-validation-choice-group">
<div class="js-survey-validation-group-title">Would you recommend this service?</div>
<div class="js-survey-validation-recommend-grid">
<label class="js-survey-validation-radio-card">
<input type="radio" name="recommend" value="Yes">
<span>Yes</span>
</label>
<label class="js-survey-validation-radio-card">
<input type="radio" name="recommend" value="Maybe">
<span>Maybe</span>
</label>
<label class="js-survey-validation-radio-card">
<input type="radio" name="recommend" value="No">
<span>No</span>
</label>
</div>
<div class="js-survey-validation-group-message" data-survey-message="recommend">Choose one recommendation answer.</div>
</div>
<div class="js-survey-validation-field" data-survey-field="feedback">
<label for="survey-validation-feedback">Feedback message</label>
<textarea id="survey-validation-feedback" name="feedback" placeholder="Write at least 20 characters of feedback..."></textarea>
<div class="js-survey-validation-message" data-survey-message="feedback"></div>
</div>
<button class="js-survey-validation-submit" type="submit">Validate Survey</button>
<div class="js-survey-validation-result" data-js-survey-result>
Survey validation passed. This feedback can now be saved or sent to your survey backend.
</div>
</form>
</div>
<aside class="js-survey-validation-summary">
<div>
<div class="js-survey-validation-kicker">Survey Summary</div>
<h3>Review answers before submitting</h3>
<p>Live summaries help users see whether their required survey answers are complete.</p>
</div>
<div class="js-survey-validation-summary-box">
<div class="js-survey-validation-summary-line">
<span>Email</span>
<strong data-js-survey-preview-email>Not entered yet</strong>
</div>
<div class="js-survey-validation-summary-line">
<span>Rating</span>
<strong data-js-survey-preview-rating>No rating selected</strong>
</div>
<div class="js-survey-validation-summary-line">
<span>Features</span>
<strong data-js-survey-preview-features>No features selected</strong>
</div>
<div class="js-survey-validation-summary-line">
<span>Recommendation</span>
<strong data-js-survey-preview-recommend>No answer selected</strong>
</div>
</div>
<div class="js-survey-validation-stats">
<div class="js-survey-validation-stat">
<strong data-js-survey-stat-count>0</strong>
<span>selected features</span>
</div>
<div class="js-survey-validation-stat">
<strong data-js-survey-stat-rating>--</strong>
<span>satisfaction rating</span>
</div>
</div>
</aside>
</div>
CSS
.js-survey-validation-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 26px;
align-items: stretch;
min-height: 720px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(14, 165, 233, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(34, 197, 94, 0.14), transparent 34%),
linear-gradient(135deg, #f0f9ff 0%, #ffffff 52%, #f0fdf4 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-survey-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-survey-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-survey-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-survey-validation-form {
display: grid;
gap: 18px;
}
.js-survey-validation-field {
display: grid;
gap: 8px;
}
.js-survey-validation-field label,
.js-survey-validation-group-title {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-survey-validation-field input[type="email"],
.js-survey-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-survey-validation-field textarea {
min-height: 120px;
resize: vertical;
}
.js-survey-validation-field input[type="email"]:focus,
.js-survey-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(14, 165, 233, 0.62);
box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.11);
}
.js-survey-validation-field.is-error input[type="email"],
.js-survey-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-survey-validation-field.is-valid input[type="email"],
.js-survey-validation-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-survey-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-survey-validation-field.is-valid .js-survey-validation-message {
color: #15803d;
}
.js-survey-validation-choice-group {
display: grid;
gap: 10px;
}
.js-survey-validation-rating-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px;
}
.js-survey-validation-radio-card,
.js-survey-validation-checkbox-card {
position: relative;
}
.js-survey-validation-radio-card input,
.js-survey-validation-checkbox-card input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.js-survey-validation-radio-card span,
.js-survey-validation-checkbox-card span {
display: grid;
place-items: center;
min-height: 60px;
padding: 12px;
border-radius: 18px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.10);
color: #334155;
font-size: 14px;
line-height: 1.35;
font-weight: 900;
text-align: center;
cursor: pointer;
transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}
.js-survey-validation-radio-card input:checked + span,
.js-survey-validation-checkbox-card input:checked + span {
background: #ecfeff;
border-color: rgba(14, 165, 233, 0.48);
color: #0e7490;
box-shadow: 0 14px 30px rgba(14, 165, 233, 0.12);
transform: translateY(-1px);
}
.js-survey-validation-feature-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.js-survey-validation-checkbox-card span {
min-height: 72px;
place-items: start;
text-align: left;
}
.js-survey-validation-checkbox-card strong {
display: block;
margin-bottom: 4px;
color: #111827;
font-size: 14px;
line-height: 1.2;
font-weight: 950;
}
.js-survey-validation-checkbox-card small {
display: block;
color: #64748b;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
.js-survey-validation-recommend-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.js-survey-validation-group-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-survey-validation-group-message.is-valid {
color: #15803d;
}
.js-survey-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #0ea5e9, #22c55e);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(14, 165, 233, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-survey-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(14, 165, 233, 0.33);
}
.js-survey-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-survey-validation-result.is-visible {
display: block;
}
.js-survey-validation-summary {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255,255,255,0.20), transparent 34%),
linear-gradient(135deg, #164e63 0%, #0284c7 48%, #16a34a 100%);
box-shadow: 0 26px 80px rgba(14, 165, 233, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-survey-validation-summary::before {
content: "";
position: absolute;
right: -120px;
top: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255,255,255,0.12);
}
.js-survey-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255,255,255,0.14);
border: 1px solid rgba(255,255,255,0.18);
color: #cffafe;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-survey-validation-summary h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-survey-validation-summary p {
position: relative;
z-index: 2;
margin: 0;
color: #cffafe;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-survey-validation-summary-box {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
padding: 18px;
border-radius: 24px;
background: rgba(255,255,255,0.13);
border: 1px solid rgba(255,255,255,0.15);
}
.js-survey-validation-summary-line {
display: grid;
gap: 4px;
}
.js-survey-validation-summary-line span {
color: #cffafe;
font-size: 11px;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-survey-validation-summary-line strong {
color: #ffffff;
font-size: 15px;
line-height: 1.35;
font-weight: 900;
overflow-wrap: anywhere;
}
.js-survey-validation-stats {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.js-survey-validation-stat {
padding: 16px;
border-radius: 20px;
background: rgba(255,255,255,0.13);
border: 1px solid rgba(255,255,255,0.14);
}
.js-survey-validation-stat strong {
display: block;
margin-bottom: 5px;
color: #ffffff;
font-size: 30px;
line-height: 1;
font-weight: 950;
letter-spacing: -0.05em;
}
.js-survey-validation-stat span {
display: block;
color: #cffafe;
font-size: 12px;
line-height: 1.35;
font-weight: 750;
}
@media (max-width: 940px) {
.js-survey-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-survey-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-survey-validation-card,
.js-survey-validation-summary {
padding: 24px;
border-radius: 24px;
}
.js-survey-validation-rating-grid,
.js-survey-validation-feature-grid,
.js-survey-validation-recommend-grid,
.js-survey-validation-stats {
grid-template-columns: 1fr;
}
.js-survey-validation-card h3,
.js-survey-validation-summary h3 {
font-size: 30px;
}
}
This survey form validation example is useful for customer feedback forms, satisfaction surveys, product research forms, onboarding questionnaires, website feedback widgets, client review forms, and user experience surveys.
24. File Upload Validation
File upload validation helps prevent users from attaching the wrong file type or uploading files that are too large. This pattern is useful for contact forms, job application forms, quote request forms, support tickets, document upload forms, and profile settings.
This example validates file type, file size, required description, and email address. It also shows a polished upload summary with selected file name, file size, file type, and validation status before submission.
- Validates allowed file types
- Checks maximum file size
- Shows selected file details before submit
- Validates email and upload description
- Good for job applications, support tickets, quote forms, and document uploads
Upload document
Attach a valid file and add a short description before submitting.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-upload-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-upload-validation-form]");
const result = demo.querySelector("[data-js-upload-result]");
const drop = demo.querySelector("[data-js-upload-drop]");
const fileNamePreview = demo.querySelector("[data-js-upload-file-name]");
const fileDetailsPreview = demo.querySelector("[data-js-upload-file-details]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
description: form.querySelector("[name='description']"),
file: form.querySelector("[name='file']")
};
const allowedExtensions = ["pdf", "doc", "docx", "png", "jpg", "jpeg"];
const maxSize = 2 * 1024 * 1024;
function formatFileSize(bytes) {
if (bytes < 1024) {
return bytes + " B";
}
if (bytes < 1024 * 1024) {
return (bytes / 1024).toFixed(1) + " KB";
}
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
}
function getFileExtension(fileName) {
return fileName.split(".").pop().toLowerCase();
}
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-upload-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-upload-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
}
function setFileState(isValid, message) {
const messageBox = demo.querySelector("[data-upload-message='file']");
drop.classList.remove("is-valid", "is-error");
drop.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
messageBox.classList.toggle("is-valid", isValid);
}
function resetFilePreview() {
fileNamePreview.textContent = "No file selected";
fileDetailsPreview.textContent = "Allowed: PDF, DOC, DOCX, PNG, JPG. Maximum size: 2 MB.";
}
function updateFilePreview(file) {
if (!file) {
resetFilePreview();
return;
}
fileNamePreview.textContent = file.name;
fileDetailsPreview.textContent = getFileExtension(file.name).toUpperCase() + " file · " + formatFileSize(file.size);
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your full name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateDescription() {
if (fields.description.value.trim().length < 10) {
setField("description", false, "Write at least 10 characters.");
return false;
}
setField("description", true, "Description looks good.");
return true;
}
function validateFile() {
const file = fields.file.files[0];
updateFilePreview(file);
if (!file) {
setFileState(false, "A file is required.");
return false;
}
const extension = getFileExtension(file.name);
if (!allowedExtensions.includes(extension)) {
setFileState(false, "This file type is not allowed.");
return false;
}
if (file.size > maxSize) {
setFileState(false, "File is too large. Maximum size is 2 MB.");
return false;
}
setFileState(true, "File looks valid: " + file.name + " (" + formatFileSize(file.size) + ").");
return true;
}
function validateAll() {
return validateName() &&
validateEmail() &&
validateDescription() &&
validateFile();
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.description.addEventListener("input", function () {
validateDescription();
result.classList.remove("is-visible");
});
fields.file.addEventListener("change", function () {
validateFile();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
resetFilePreview();
});
})();
HTML
<div class="js-upload-validation-preview" data-js-upload-validation-demo>
<aside class="js-upload-validation-info">
<div>
<div class="js-upload-validation-kicker">Upload Validation</div>
<h3>Validate files before form submission</h3>
<p>Check file type, size, contact details, and description before sending uploads to your backend.</p>
</div>
<div class="js-upload-validation-file-card">
<div class="js-upload-validation-file-icon">↥</div>
<div>
<strong data-js-upload-file-name>No file selected</strong>
<span data-js-upload-file-details>Allowed: PDF, DOC, DOCX, PNG, JPG. Maximum size: 2 MB.</span>
</div>
</div>
<div class="js-upload-validation-rules">
<div class="js-upload-validation-rule">
<div class="js-upload-validation-rule-icon">2MB</div>
<div>
<strong>Size limit</strong>
<span>Reject files larger than 2 MB.</span>
</div>
</div>
<div class="js-upload-validation-rule">
<div class="js-upload-validation-rule-icon">✓</div>
<div>
<strong>Allowed types</strong>
<span>PDF, DOC, DOCX, PNG, and JPG only.</span>
</div>
</div>
</div>
</aside>
<div class="js-upload-validation-card">
<h3>Upload document</h3>
<p class="js-upload-validation-text">Attach a valid file and add a short description before submitting.</p>
<form class="js-upload-validation-form" novalidate data-js-upload-validation-form>
<div class="js-upload-validation-grid">
<div class="js-upload-validation-field" data-upload-field="name">
<label for="upload-validation-name">Full name</label>
<input id="upload-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-upload-validation-message" data-upload-message="name"></div>
</div>
<div class="js-upload-validation-field" data-upload-field="email">
<label for="upload-validation-email">Email address</label>
<input id="upload-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-upload-validation-message" data-upload-message="email"></div>
</div>
<div class="js-upload-validation-field is-full" data-upload-field="description">
<label for="upload-validation-description">Upload description</label>
<textarea id="upload-validation-description" name="description" placeholder="Explain what this file is for..."></textarea>
<div class="js-upload-validation-message" data-upload-message="description"></div>
</div>
</div>
<div class="js-upload-validation-drop" data-js-upload-drop>
<input id="upload-validation-file" name="file" type="file" accept=".pdf,.doc,.docx,.png,.jpg,.jpeg">
<div class="js-upload-validation-drop-content">
<div class="js-upload-validation-drop-icon">↥</div>
<strong>Choose a file to upload</strong>
<span>PDF, DOC, DOCX, PNG, JPG. Max 2 MB.</span>
</div>
</div>
<div class="js-upload-validation-file-message" data-upload-message="file">A file is required.</div>
<button class="js-upload-validation-submit" type="submit">Validate Upload</button>
<div class="js-upload-validation-result" data-js-upload-result>
File upload validation passed. This upload form can now be connected to a secure backend handler.
</div>
</form>
</div>
</div>
CSS
.js-upload-validation-preview {
display: grid;
grid-template-columns: 360px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
min-height: 700px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.14), transparent 34%),
linear-gradient(135deg, #eef2ff 0%, #ffffff 52%, #f0f9ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-upload-validation-info {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255,255,255,0.20), transparent 34%),
linear-gradient(135deg, #312e81 0%, #4f46e5 48%, #0284c7 100%);
box-shadow: 0 26px 80px rgba(79, 70, 229, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-upload-validation-info::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 290px;
height: 290px;
border-radius: 999px;
background: rgba(255,255,255,0.12);
}
.js-upload-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255,255,255,0.14);
border: 1px solid rgba(255,255,255,0.18);
color: #e0e7ff;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-upload-validation-info h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-upload-validation-info p {
position: relative;
z-index: 2;
margin: 0;
color: #dbeafe;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-upload-validation-file-card {
position: relative;
z-index: 2;
display: grid;
gap: 16px;
padding: 22px;
border-radius: 26px;
background: rgba(255,255,255,0.13);
border: 1px solid rgba(255,255,255,0.16);
}
.js-upload-validation-file-icon {
display: grid;
place-items: center;
width: 78px;
height: 78px;
border-radius: 26px;
background: rgba(255,255,255,0.18);
color: #ffffff;
font-size: 34px;
font-weight: 950;
}
.js-upload-validation-file-card strong {
display: block;
color: #ffffff;
font-size: 18px;
line-height: 1.2;
font-weight: 950;
overflow-wrap: anywhere;
}
.js-upload-validation-file-card span {
display: block;
color: #dbeafe;
font-size: 13px;
line-height: 1.45;
font-weight: 750;
}
.js-upload-validation-rules {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
}
.js-upload-validation-rule {
display: grid;
grid-template-columns: 42px 1fr;
gap: 12px;
align-items: center;
padding: 13px;
border-radius: 18px;
background: rgba(255,255,255,0.13);
border: 1px solid rgba(255,255,255,0.14);
}
.js-upload-validation-rule-icon {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 15px;
background: rgba(255,255,255,0.16);
color: #ffffff;
font-size: 18px;
}
.js-upload-validation-rule strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 13px;
line-height: 1.2;
font-weight: 950;
}
.js-upload-validation-rule span {
display: block;
color: #dbeafe;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
.js-upload-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-upload-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-upload-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-upload-validation-form {
display: grid;
gap: 16px;
}
.js-upload-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-upload-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-upload-validation-field {
display: grid;
gap: 7px;
}
.js-upload-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-upload-validation-field input[type="text"],
.js-upload-validation-field input[type="email"],
.js-upload-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-upload-validation-field textarea {
min-height: 110px;
resize: vertical;
}
.js-upload-validation-field input[type="text"]:focus,
.js-upload-validation-field input[type="email"]:focus,
.js-upload-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(79, 70, 229, 0.62);
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.11);
}
.js-upload-validation-field.is-error input[type="text"],
.js-upload-validation-field.is-error input[type="email"],
.js-upload-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-upload-validation-field.is-valid input[type="text"],
.js-upload-validation-field.is-valid input[type="email"],
.js-upload-validation-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-upload-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-upload-validation-field.is-valid .js-upload-validation-message {
color: #15803d;
}
.js-upload-validation-drop {
position: relative;
display: grid;
place-items: center;
min-height: 170px;
padding: 22px;
border: 2px dashed rgba(79, 70, 229, 0.28);
border-radius: 26px;
background: #eef2ff;
text-align: center;
transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease;
}
.js-upload-validation-drop.is-valid {
background: #f0fdf4;
border-color: rgba(34, 197, 94, 0.42);
}
.js-upload-validation-drop.is-error {
background: #fff1f2;
border-color: rgba(239, 68, 68, 0.42);
}
.js-upload-validation-drop input {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
}
.js-upload-validation-drop-content {
pointer-events: none;
}
.js-upload-validation-drop-icon {
display: grid;
place-items: center;
width: 62px;
height: 62px;
margin: 0 auto 12px;
border-radius: 22px;
background: #ffffff;
color: #4f46e5;
font-size: 28px;
box-shadow: 0 14px 30px rgba(79, 70, 229, 0.13);
}
.js-upload-validation-drop strong {
display: block;
margin-bottom: 6px;
color: #111827;
font-size: 16px;
line-height: 1.2;
font-weight: 950;
}
.js-upload-validation-drop span {
display: block;
color: #64748b;
font-size: 13px;
line-height: 1.45;
font-weight: 750;
}
.js-upload-validation-file-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-upload-validation-file-message.is-valid {
color: #15803d;
}
.js-upload-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #4f46e5, #0284c7);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(79, 70, 229, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-upload-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(79, 70, 229, 0.33);
}
.js-upload-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-upload-validation-result.is-visible {
display: block;
}
@media (max-width: 940px) {
.js-upload-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-upload-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-upload-validation-info,
.js-upload-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-upload-validation-grid {
grid-template-columns: 1fr;
}
.js-upload-validation-grid .is-full {
grid-column: auto;
}
.js-upload-validation-card h3,
.js-upload-validation-info h3 {
font-size: 30px;
}
}
This file upload validation example is useful for job application forms, quote request forms, contact forms, support tickets, document upload portals, profile settings, warranty claims, and client onboarding forms.
25. Date Range Booking Validation
Date range booking validation is useful for hotel booking forms, appointment forms, rental forms, event registration, travel websites, and service reservation flows. It helps users choose a valid start date and end date before submitting a booking request.
This example validates required check-in and check-out dates, blocks past dates, makes sure the end date is after the start date, calculates the number of nights, validates guest count, and updates a live booking summary.
- Validates start and end date fields
- Blocks past dates with JavaScript
- Checks that the end date is after the start date
- Calculates booking length automatically
- Good for hotels, rentals, appointments, events, and booking forms
Booking request
Choose valid booking dates, guest count, and contact details before submitting.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-date-range-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-date-range-validation-form]");
const result = demo.querySelector("[data-js-date-range-result]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
checkin: form.querySelector("[name='checkin']"),
checkout: form.querySelector("[name='checkout']"),
guests: form.querySelector("[name='guests']"),
notes: form.querySelector("[name='notes']")
};
const preview = {
nights: demo.querySelector("[data-js-date-range-nights]"),
nightLabel: demo.querySelector("[data-js-date-range-night-label]"),
dates: demo.querySelector("[data-js-date-range-preview-dates]"),
guests: demo.querySelector("[data-js-date-range-preview-guests]"),
total: demo.querySelector("[data-js-date-range-preview-total]"),
totalInline: demo.querySelector("[data-js-date-range-total]")
};
const nightlyRate = 95;
const today = new Date();
const todayString = today.toISOString().split("T")[0];
fields.checkin.min = todayString;
fields.checkout.min = todayString;
function formatMoney(value) {
return "$" + value.toFixed(2);
}
function getDateObject(value) {
return value ? new Date(value + "T00:00:00") : null;
}
function getNights() {
const checkin = getDateObject(fields.checkin.value);
const checkout = getDateObject(fields.checkout.value);
if (!checkin || !checkout) {
return 0;
}
const difference = checkout.getTime() - checkin.getTime();
return Math.round(difference / (1000 * 60 * 60 * 24));
}
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-date-range-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-date-range-message='" + fieldName + "']");
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
messageBox.textContent = message;
}
function updateSummary() {
const nights = Math.max(0, getNights());
const total = nights * nightlyRate;
const guestsText = fields.guests.value ? fields.guests.value + " guest" + (fields.guests.value === "1" ? "" : "s") : "No guests selected";
preview.nights.textContent = String(nights);
preview.nightLabel.textContent = nights === 1 ? "night selected" : "nights selected";
preview.dates.textContent = fields.checkin.value && fields.checkout.value ? fields.checkin.value + " → " + fields.checkout.value : "No dates selected";
preview.guests.textContent = guestsText;
preview.total.textContent = formatMoney(total);
preview.totalInline.textContent = formatMoney(total);
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your full name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateCheckin() {
if (!fields.checkin.value) {
setField("checkin", false, "Choose a check-in date.");
return false;
}
if (fields.checkin.value < todayString) {
setField("checkin", false, "Check-in date cannot be in the past.");
return false;
}
fields.checkout.min = fields.checkin.value;
setField("checkin", true, "Check-in date looks valid.");
return true;
}
function validateCheckout() {
if (!fields.checkout.value) {
setField("checkout", false, "Choose a check-out date.");
return false;
}
if (fields.checkout.value <= fields.checkin.value) {
setField("checkout", false, "Check-out must be after check-in.");
return false;
}
setField("checkout", true, "Check-out date looks valid.");
return true;
}
function validateGuests() {
if (!fields.guests.value) {
setField("guests", false, "Choose the number of guests.");
return false;
}
setField("guests", true, "Guest count selected.");
return true;
}
function validateNotes() {
if (fields.notes.value.trim().length < 5) {
setField("notes", false, "Add at least 5 characters of booking notes.");
return false;
}
setField("notes", true, "Booking notes look good.");
return true;
}
function validateAll() {
return validateName() &&
validateEmail() &&
validateCheckin() &&
validateCheckout() &&
validateGuests() &&
validateNotes();
}
Object.keys(fields).forEach(function (key) {
const eventType = fields[key].tagName === "SELECT" || fields[key].type === "date" ? "change" : "input";
fields[key].addEventListener(eventType, function () {
result.classList.remove("is-visible");
if (key === "name") validateName();
if (key === "email") validateEmail();
if (key === "checkin") {
validateCheckin();
if (fields.checkout.value) validateCheckout();
}
if (key === "checkout") validateCheckout();
if (key === "guests") validateGuests();
if (key === "notes") validateNotes();
updateSummary();
});
});
form.addEventListener("submit", function (event) {
event.preventDefault();
updateSummary();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateSummary();
});
})();
HTML
<div class="js-date-range-validation-preview" data-js-date-range-validation-demo>
<div class="js-date-range-validation-card">
<h3>Booking request</h3>
<p class="js-date-range-validation-text">Choose valid booking dates, guest count, and contact details before submitting.</p>
<form class="js-date-range-validation-form" novalidate data-js-date-range-validation-form>
<div class="js-date-range-validation-grid">
<div class="js-date-range-validation-field" data-date-range-field="name">
<label for="date-range-validation-name">Full name</label>
<input id="date-range-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-date-range-validation-message" data-date-range-message="name"></div>
</div>
<div class="js-date-range-validation-field" data-date-range-field="email">
<label for="date-range-validation-email">Email address</label>
<input id="date-range-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-date-range-validation-message" data-date-range-message="email"></div>
</div>
<div class="js-date-range-validation-field" data-date-range-field="checkin">
<label for="date-range-validation-checkin">Check-in date</label>
<input id="date-range-validation-checkin" name="checkin" type="date">
<div class="js-date-range-validation-message" data-date-range-message="checkin"></div>
</div>
<div class="js-date-range-validation-field" data-date-range-field="checkout">
<label for="date-range-validation-checkout">Check-out date</label>
<input id="date-range-validation-checkout" name="checkout" type="date">
<div class="js-date-range-validation-message" data-date-range-message="checkout"></div>
</div>
<div class="js-date-range-validation-field" data-date-range-field="guests">
<label for="date-range-validation-guests">Guests</label>
<select id="date-range-validation-guests" name="guests">
<option value="">Choose guests</option>
<option value="1">1 guest</option>
<option value="2">2 guests</option>
<option value="3">3 guests</option>
<option value="4">4 guests</option>
<option value="5">5 guests</option>
</select>
<div class="js-date-range-validation-message" data-date-range-message="guests"></div>
</div>
<div class="js-date-range-validation-field is-full" data-date-range-field="notes">
<label for="date-range-validation-notes">Booking notes</label>
<textarea id="date-range-validation-notes" name="notes" placeholder="Add arrival time, room preference, or special requests..."></textarea>
<div class="js-date-range-validation-message" data-date-range-message="notes"></div>
</div>
</div>
<div class="js-date-range-validation-price-box">
<div class="js-date-range-validation-price-row">
<span>Nightly rate</span>
<strong>$95.00</strong>
</div>
<div class="js-date-range-validation-price-row">
<span>Estimated total</span>
<strong data-js-date-range-total>$0.00</strong>
</div>
</div>
<button class="js-date-range-validation-submit" type="submit">Validate Booking</button>
<div class="js-date-range-validation-result" data-js-date-range-result>
Date range validation passed. This booking request can now be sent to your reservation system.
</div>
</form>
</div>
<aside class="js-date-range-validation-summary">
<div>
<div class="js-date-range-validation-kicker">Booking Summary</div>
<h3>Validate dates before reservation</h3>
<p>The summary updates when users choose valid booking dates and guest details.</p>
</div>
<div class="js-date-range-validation-night-box">
<div>
<span class="js-date-range-validation-night-number" data-js-date-range-nights>0</span>
<span class="js-date-range-validation-night-label" data-js-date-range-night-label>nights selected</span>
</div>
</div>
<div class="js-date-range-validation-summary-box">
<div class="js-date-range-validation-summary-line">
<span>Dates</span>
<strong data-js-date-range-preview-dates>No dates selected</strong>
</div>
<div class="js-date-range-validation-summary-line">
<span>Guests</span>
<strong data-js-date-range-preview-guests>No guests selected</strong>
</div>
<div class="js-date-range-validation-summary-line">
<span>Total</span>
<strong data-js-date-range-preview-total>$0.00</strong>
</div>
</div>
</aside>
</div>
CSS
.js-date-range-validation-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 26px;
align-items: stretch;
min-height: 680px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(245, 158, 11, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(20, 184, 166, 0.14), transparent 34%),
linear-gradient(135deg, #fffbeb 0%, #ffffff 52%, #f0fdfa 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-date-range-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-date-range-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-date-range-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-date-range-validation-form {
display: grid;
gap: 16px;
}
.js-date-range-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-date-range-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-date-range-validation-field {
display: grid;
gap: 7px;
}
.js-date-range-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-date-range-validation-field input,
.js-date-range-validation-field select,
.js-date-range-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-date-range-validation-field textarea {
min-height: 110px;
resize: vertical;
}
.js-date-range-validation-field input:focus,
.js-date-range-validation-field select:focus,
.js-date-range-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(245, 158, 11, 0.62);
box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.12);
}
.js-date-range-validation-field.is-error input,
.js-date-range-validation-field.is-error select,
.js-date-range-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-date-range-validation-field.is-valid input,
.js-date-range-validation-field.is-valid select,
.js-date-range-validation-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-date-range-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-date-range-validation-field.is-valid .js-date-range-validation-message {
color: #15803d;
}
.js-date-range-validation-price-box {
display: grid;
gap: 10px;
padding: 16px;
border-radius: 22px;
background: #fffbeb;
border: 1px solid rgba(245, 158, 11, 0.20);
}
.js-date-range-validation-price-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
color: #92400e;
font-size: 14px;
line-height: 1.35;
font-weight: 850;
}
.js-date-range-validation-price-row strong {
color: #111827;
font-weight: 950;
}
.js-date-range-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #f59e0b, #14b8a6);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(245, 158, 11, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-date-range-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(245, 158, 11, 0.33);
}
.js-date-range-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-date-range-validation-result.is-visible {
display: block;
}
.js-date-range-validation-summary {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #78350f 0%, #f59e0b 48%, #0f766e 100%);
box-shadow: 0 26px 80px rgba(245, 158, 11, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-date-range-validation-summary::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-date-range-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #fef3c7;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-date-range-validation-summary h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-date-range-validation-summary p {
position: relative;
z-index: 2;
margin: 0;
color: #fef3c7;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-date-range-validation-night-box {
position: relative;
z-index: 2;
display: grid;
place-items: center;
min-height: 220px;
padding: 24px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
text-align: center;
}
.js-date-range-validation-night-number {
display: block;
margin-bottom: 10px;
color: #ffffff;
font-size: 82px;
line-height: 0.95;
font-weight: 950;
letter-spacing: -0.08em;
}
.js-date-range-validation-night-label {
display: block;
color: #fef3c7;
font-size: 14px;
line-height: 1.45;
font-weight: 850;
}
.js-date-range-validation-summary-box {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
padding: 18px;
border-radius: 24px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-date-range-validation-summary-line {
display: grid;
gap: 4px;
}
.js-date-range-validation-summary-line span {
color: #fef3c7;
font-size: 11px;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-date-range-validation-summary-line strong {
color: #ffffff;
font-size: 15px;
line-height: 1.35;
font-weight: 900;
overflow-wrap: anywhere;
}
@media (max-width: 940px) {
.js-date-range-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-date-range-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-date-range-validation-card,
.js-date-range-validation-summary {
padding: 24px;
border-radius: 24px;
}
.js-date-range-validation-grid {
grid-template-columns: 1fr;
}
.js-date-range-validation-grid .is-full {
grid-column: auto;
}
.js-date-range-validation-card h3,
.js-date-range-validation-summary h3 {
font-size: 30px;
}
}
This date range validation example is useful for hotel booking forms, vacation rentals, appointment requests, event registration, travel search forms, service reservations, equipment rentals, and calendar-based checkout flows.
26. Quantity and Order Validation
Quantity and order validation helps ecommerce product forms, wholesale order forms, donation forms, pricing calculators, and quote request forms prevent invalid order values. It makes sure users select valid quantities, options, and contact details before submitting.
This example validates product quantity, product option, delivery method, email address, and a required confirmation checkbox. It also calculates a live order total and blocks quantities outside the allowed range.
- Validates minimum and maximum quantity
- Updates order total in real time
- Validates product option and delivery method
- Includes required confirmation checkbox validation
- Good for ecommerce, wholesale forms, quote forms, and pricing calculators
Order details
Choose a valid quantity, license type, delivery method, and confirm the order details.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-quantity-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-quantity-validation-form]");
const result = demo.querySelector("[data-js-quantity-result]");
const minusButton = demo.querySelector("[data-js-quantity-minus]");
const plusButton = demo.querySelector("[data-js-quantity-plus]");
const fields = {
email: form.querySelector("[name='email']"),
quantity: form.querySelector("[name='quantity']"),
license: form.querySelector("[name='license']"),
delivery: form.querySelector("[name='delivery']"),
confirm: form.querySelector("[name='confirm']")
};
const preview = {
quantity: demo.querySelector("[data-js-quantity-preview-qty]"),
license: demo.querySelector("[data-js-quantity-preview-license]"),
delivery: demo.querySelector("[data-js-quantity-preview-delivery]"),
total: demo.querySelector("[data-js-quantity-preview-total]"),
formTotal: demo.querySelector("[data-js-quantity-form-total]")
};
const unitPrice = 39;
const minQty = 1;
const maxQty = 20;
function formatMoney(value) {
return "$" + value.toFixed(2);
}
function getQuantity() {
return Number(fields.quantity.value || 0);
}
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-quantity-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-quantity-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
}
function updateSummary() {
const quantity = Math.max(0, getQuantity());
const total = quantity * unitPrice;
preview.quantity.textContent = quantity ? String(quantity) : "0";
preview.license.textContent = fields.license.value || "Not selected";
preview.delivery.textContent = fields.delivery.value || "Not selected";
preview.total.textContent = formatMoney(total);
preview.formTotal.textContent = formatMoney(total);
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateQuantity() {
const quantity = getQuantity();
if (!Number.isInteger(quantity)) {
setField("quantity", false, "Quantity must be a whole number.");
return false;
}
if (quantity < minQty) {
setField("quantity", false, "Minimum quantity is " + minQty + ".");
return false;
}
if (quantity > maxQty) {
setField("quantity", false, "Maximum quantity is " + maxQty + ".");
return false;
}
setField("quantity", true, "Quantity looks valid.");
return true;
}
function validateLicense() {
if (!fields.license.value) {
setField("license", false, "Choose a license type.");
return false;
}
setField("license", true, "License selected.");
return true;
}
function validateDelivery() {
if (!fields.delivery.value) {
setField("delivery", false, "Choose a delivery method.");
return false;
}
setField("delivery", true, "Delivery method selected.");
return true;
}
function validateConfirm() {
const messageBox = demo.querySelector("[data-quantity-message='confirm']");
if (!fields.confirm.checked) {
messageBox.textContent = "Please confirm the order details.";
messageBox.style.color = "#dc2626";
return false;
}
messageBox.textContent = "Order confirmation accepted.";
messageBox.style.color = "#15803d";
return true;
}
function validateAll() {
return validateEmail() &&
validateQuantity() &&
validateLicense() &&
validateDelivery() &&
validateConfirm();
}
minusButton.addEventListener("click", function () {
const nextValue = Math.max(minQty, getQuantity() - 1);
fields.quantity.value = String(nextValue);
validateQuantity();
updateSummary();
result.classList.remove("is-visible");
});
plusButton.addEventListener("click", function () {
const nextValue = Math.min(maxQty, getQuantity() + 1);
fields.quantity.value = String(nextValue);
validateQuantity();
updateSummary();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.quantity.addEventListener("input", function () {
validateQuantity();
updateSummary();
result.classList.remove("is-visible");
});
fields.license.addEventListener("change", function () {
validateLicense();
updateSummary();
result.classList.remove("is-visible");
});
fields.delivery.addEventListener("change", function () {
validateDelivery();
updateSummary();
result.classList.remove("is-visible");
});
fields.confirm.addEventListener("change", function () {
validateConfirm();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
updateSummary();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateSummary();
});
})();
HTML
<div class="js-quantity-validation-preview" data-js-quantity-validation-demo>
<aside class="js-quantity-validation-product">
<div>
<div class="js-quantity-validation-kicker">Order Validation</div>
<h3>Validate quantities before checkout</h3>
<p>Prevent invalid product quantities and missing order options before the order form is submitted.</p>
</div>
<div class="js-quantity-validation-product-visual">
<div>
<div class="js-quantity-validation-box-icon">▣</div>
<strong>Premium UI Kit</strong>
<span>$39 per license · 1-20 licenses allowed</span>
</div>
</div>
<div class="js-quantity-validation-summary-box">
<div class="js-quantity-validation-summary-line">
<span>Quantity</span>
<strong data-js-quantity-preview-qty>1</strong>
</div>
<div class="js-quantity-validation-summary-line">
<span>License</span>
<strong data-js-quantity-preview-license>Not selected</strong>
</div>
<div class="js-quantity-validation-summary-line">
<span>Delivery</span>
<strong data-js-quantity-preview-delivery>Not selected</strong>
</div>
<div class="js-quantity-validation-summary-line is-total">
<span>Total</span>
<strong data-js-quantity-preview-total>$39.00</strong>
</div>
</div>
</aside>
<div class="js-quantity-validation-card">
<h3>Order details</h3>
<p class="js-quantity-validation-text">Choose a valid quantity, license type, delivery method, and confirm the order details.</p>
<form class="js-quantity-validation-form" novalidate data-js-quantity-validation-form>
<div class="js-quantity-validation-grid">
<div class="js-quantity-validation-field is-full" data-quantity-field="email">
<label for="quantity-validation-email">Email address</label>
<input id="quantity-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-quantity-validation-message" data-quantity-message="email"></div>
</div>
<div class="js-quantity-validation-field is-full" data-quantity-field="quantity">
<label for="quantity-validation-quantity">Quantity</label>
<div class="js-quantity-validation-stepper">
<button type="button" data-js-quantity-minus>-</button>
<input id="quantity-validation-quantity" name="quantity" type="number" min="1" max="20" value="1">
<button type="button" data-js-quantity-plus>+</button>
</div>
<div class="js-quantity-validation-message" data-quantity-message="quantity"></div>
</div>
<div class="js-quantity-validation-field" data-quantity-field="license">
<label for="quantity-validation-license">License type</label>
<select id="quantity-validation-license" name="license">
<option value="">Choose license</option>
<option value="Personal">Personal</option>
<option value="Commercial">Commercial</option>
<option value="Extended">Extended</option>
</select>
<div class="js-quantity-validation-message" data-quantity-message="license"></div>
</div>
<div class="js-quantity-validation-field" data-quantity-field="delivery">
<label for="quantity-validation-delivery">Delivery method</label>
<select id="quantity-validation-delivery" name="delivery">
<option value="">Choose delivery</option>
<option value="Email delivery">Email delivery</option>
<option value="Account download">Account download</option>
<option value="Both">Both</option>
</select>
<div class="js-quantity-validation-message" data-quantity-message="delivery"></div>
</div>
</div>
<div class="js-quantity-validation-mini-total">
<span>Live order total</span>
<strong data-js-quantity-form-total>$39.00</strong>
</div>
<div class="js-quantity-validation-confirm">
<input id="quantity-validation-confirm" name="confirm" type="checkbox">
<label for="quantity-validation-confirm">I confirm that the order quantity and selected options are correct.</label>
<div class="js-quantity-validation-confirm-message" data-quantity-message="confirm"></div>
</div>
<button class="js-quantity-validation-submit" type="submit">Validate Order</button>
<div class="js-quantity-validation-result" data-js-quantity-result>
Quantity and order validation passed. This order can now continue to checkout.
</div>
</form>
</div>
</div>
CSS
.js-quantity-validation-preview {
display: grid;
grid-template-columns: 360px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
min-height: 680px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(34, 197, 94, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(59, 130, 246, 0.14), transparent 34%),
linear-gradient(135deg, #f0fdf4 0%, #ffffff 52%, #eff6ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-quantity-validation-product {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #14532d 0%, #16a34a 48%, #2563eb 100%);
box-shadow: 0 26px 80px rgba(34, 197, 94, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-quantity-validation-product::before {
content: "";
position: absolute;
right: -120px;
top: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-quantity-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #dcfce7;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-quantity-validation-product h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-quantity-validation-product p {
position: relative;
z-index: 2;
margin: 0;
color: #dcfce7;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-quantity-validation-product-visual {
position: relative;
z-index: 2;
display: grid;
place-items: center;
min-height: 230px;
padding: 24px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
text-align: center;
}
.js-quantity-validation-box-icon {
display: grid;
place-items: center;
width: 104px;
height: 104px;
margin-bottom: 14px;
border-radius: 34px;
background: rgba(255, 255, 255, 0.18);
color: #ffffff;
font-size: 46px;
line-height: 1;
box-shadow: 0 28px 70px rgba(0, 0, 0, 0.16);
}
.js-quantity-validation-product-visual strong {
display: block;
color: #ffffff;
font-size: 20px;
line-height: 1.2;
font-weight: 950;
}
.js-quantity-validation-product-visual span {
display: block;
margin-top: 6px;
color: #dcfce7;
font-size: 13px;
line-height: 1.4;
font-weight: 750;
}
.js-quantity-validation-summary-box {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
padding: 18px;
border-radius: 24px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-quantity-validation-summary-line {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
color: #dcfce7;
font-size: 13px;
line-height: 1.35;
font-weight: 850;
}
.js-quantity-validation-summary-line strong {
color: #ffffff;
font-size: 15px;
font-weight: 950;
}
.js-quantity-validation-summary-line.is-total {
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.18);
}
.js-quantity-validation-summary-line.is-total strong {
font-size: 26px;
letter-spacing: -0.04em;
}
.js-quantity-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-quantity-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-quantity-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-quantity-validation-form {
display: grid;
gap: 16px;
}
.js-quantity-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-quantity-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-quantity-validation-field {
display: grid;
gap: 7px;
}
.js-quantity-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-quantity-validation-field input,
.js-quantity-validation-field select {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-quantity-validation-field input:focus,
.js-quantity-validation-field select:focus {
background: #ffffff;
border-color: rgba(34, 197, 94, 0.62);
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.11);
}
.js-quantity-validation-field.is-error input,
.js-quantity-validation-field.is-error select {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-quantity-validation-field.is-valid input,
.js-quantity-validation-field.is-valid select {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-quantity-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-quantity-validation-field.is-valid .js-quantity-validation-message {
color: #15803d;
}
.js-quantity-validation-stepper {
display: grid;
grid-template-columns: 54px minmax(0, 1fr) 54px;
gap: 10px;
align-items: center;
}
.js-quantity-validation-stepper button {
display: grid;
place-items: center;
width: 54px;
height: 54px;
border: 0;
border-radius: 18px;
background: #dcfce7;
color: #15803d;
font-size: 24px;
line-height: 1;
font-weight: 950;
cursor: pointer;
}
.js-quantity-validation-stepper input {
text-align: center;
font-size: 20px;
font-weight: 950;
}
.js-quantity-validation-confirm {
display: grid;
grid-template-columns: 22px 1fr;
gap: 12px;
padding: 15px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-quantity-validation-confirm input {
width: 19px;
height: 19px;
margin-top: 2px;
accent-color: #16a34a;
}
.js-quantity-validation-confirm label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-quantity-validation-confirm-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-quantity-validation-mini-total {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 16px;
border-radius: 22px;
background: #f0fdf4;
border: 1px solid rgba(34, 197, 94, 0.20);
color: #166534;
font-size: 14px;
line-height: 1.35;
font-weight: 850;
}
.js-quantity-validation-mini-total strong {
color: #111827;
font-size: 24px;
font-weight: 950;
letter-spacing: -0.04em;
}
.js-quantity-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #16a34a, #2563eb);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(34, 197, 94, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-quantity-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(34, 197, 94, 0.33);
}
.js-quantity-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-quantity-validation-result.is-visible {
display: block;
}
@media (max-width: 940px) {
.js-quantity-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-quantity-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-quantity-validation-product,
.js-quantity-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-quantity-validation-grid {
grid-template-columns: 1fr;
}
.js-quantity-validation-grid .is-full {
grid-column: auto;
}
.js-quantity-validation-product h3,
.js-quantity-validation-card h3 {
font-size: 30px;
}
}
This quantity and order validation example is useful for ecommerce product pages, wholesale order forms, pricing calculators, license purchase forms, donation quantity forms, quote request forms, and checkout product option validation.
27. Real-Time Form Validation Summary
Real-time form validation summary helps users understand which fields are valid, which fields still need attention, and whether the form is ready to submit. This pattern is especially useful for longer signup forms, onboarding forms, account forms, checkout forms, and application forms.
This example validates name, email, phone, website URL, company size, and required consent. A live validation summary updates as the user types, showing completed fields, missing fields, and overall form readiness.
- Shows a live validation summary beside the form
- Validates name, email, phone number, website URL, and select field
- Tracks completed and missing fields in real time
- Includes required consent validation
- Good for onboarding, account forms, checkout forms, and lead forms
Business account
Fill in the account details. The validation summary updates beside the form.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-summary-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-summary-validation-form]");
const submitButton = demo.querySelector("[data-js-summary-submit]");
const result = demo.querySelector("[data-js-summary-result]");
const score = demo.querySelector("[data-js-summary-score]");
const progress = demo.querySelector("[data-js-summary-progress]");
const readyText = demo.querySelector("[data-js-summary-ready]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
phone: form.querySelector("[name='phone']"),
website: form.querySelector("[name='website']"),
companySize: form.querySelector("[name='companySize']"),
consent: form.querySelector("[name='consent']")
};
const validationState = {
name: false,
email: false,
phone: false,
website: false,
companySize: false,
consent: false
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-summary-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-summary-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
validationState[fieldName] = isValid;
updateSummary();
}
function setConsent(isValid, message) {
const messageBox = demo.querySelector("[data-summary-message='consent']");
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
validationState.consent = isValid;
updateSummary();
}
function updateSummary() {
const keys = Object.keys(validationState);
const validCount = keys.filter(function (key) {
return validationState[key];
}).length;
const percent = Math.round((validCount / keys.length) * 100);
score.textContent = percent + "%";
progress.style.width = percent + "%";
keys.forEach(function (key) {
const item = demo.querySelector("[data-summary-item='" + key + "']");
const itemText = demo.querySelector("[data-summary-item-text='" + key + "']");
item.classList.toggle("is-valid", validationState[key]);
item.classList.toggle("is-missing", !validationState[key]);
itemText.textContent = validationState[key] ? "Complete" : "Missing";
});
submitButton.disabled = percent !== 100;
readyText.textContent = percent === 100
? "All fields are valid. The form is ready to submit."
: "Complete all required fields to unlock submit.";
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your full name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid email address.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validatePhone() {
const cleaned = fields.phone.value.replace(/[^\d+]/g, "");
if (cleaned.length < 8) {
setField("phone", false, "Enter a valid phone number.");
return false;
}
setField("phone", true, "Phone number looks valid.");
return true;
}
function validateWebsite() {
try {
const url = new URL(fields.website.value.trim());
if (url.protocol !== "http:" && url.protocol !== "https:") {
setField("website", false, "Website must start with http or https.");
return false;
}
setField("website", true, "Website URL looks valid.");
return true;
} catch (error) {
setField("website", false, "Enter a valid website URL.");
return false;
}
}
function validateCompanySize() {
if (!fields.companySize.value) {
setField("companySize", false, "Choose company size.");
return false;
}
setField("companySize", true, "Company size selected.");
return true;
}
function validateConsent() {
if (!fields.consent.checked) {
setConsent(false, "Please confirm the account request.");
return false;
}
setConsent(true, "Consent accepted.");
return true;
}
function validateAll() {
return validateName() &&
validateEmail() &&
validatePhone() &&
validateWebsite() &&
validateCompanySize() &&
validateConsent();
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.phone.addEventListener("input", function () {
validatePhone();
result.classList.remove("is-visible");
});
fields.website.addEventListener("input", function () {
validateWebsite();
result.classList.remove("is-visible");
});
fields.companySize.addEventListener("change", function () {
validateCompanySize();
result.classList.remove("is-visible");
});
fields.consent.addEventListener("change", function () {
validateConsent();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateSummary();
});
})();
HTML
<div class="js-summary-validation-preview" data-js-summary-validation-demo>
<div class="js-summary-validation-card">
<h3>Business account</h3>
<p class="js-summary-validation-text">Fill in the account details. The validation summary updates beside the form.</p>
<form class="js-summary-validation-form" novalidate data-js-summary-validation-form>
<div class="js-summary-validation-grid">
<div class="js-summary-validation-field" data-summary-field="name">
<label for="summary-validation-name">Full name</label>
<input id="summary-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-summary-validation-message" data-summary-message="name"></div>
</div>
<div class="js-summary-validation-field" data-summary-field="email">
<label for="summary-validation-email">Email address</label>
<input id="summary-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-summary-validation-message" data-summary-message="email"></div>
</div>
<div class="js-summary-validation-field" data-summary-field="phone">
<label for="summary-validation-phone">Phone number</label>
<input id="summary-validation-phone" name="phone" type="tel" placeholder="+1 555 123 4567">
<div class="js-summary-validation-message" data-summary-message="phone"></div>
</div>
<div class="js-summary-validation-field" data-summary-field="website">
<label for="summary-validation-website">Website URL</label>
<input id="summary-validation-website" name="website" type="url" placeholder="https://example.com">
<div class="js-summary-validation-message" data-summary-message="website"></div>
</div>
<div class="js-summary-validation-field is-full" data-summary-field="companySize">
<label for="summary-validation-company-size">Company size</label>
<select id="summary-validation-company-size" name="companySize">
<option value="">Choose company size</option>
<option value="1-5 people">1-5 people</option>
<option value="6-20 people">6-20 people</option>
<option value="21-100 people">21-100 people</option>
<option value="100+ people">100+ people</option>
</select>
<div class="js-summary-validation-message" data-summary-message="companySize"></div>
</div>
</div>
<div class="js-summary-validation-consent">
<input id="summary-validation-consent" name="consent" type="checkbox">
<label for="summary-validation-consent">I confirm that the information is correct and agree to be contacted about this account request.</label>
<div class="js-summary-validation-consent-message" data-summary-message="consent"></div>
</div>
<button class="js-summary-validation-submit" type="submit" data-js-summary-submit disabled>Validate Account</button>
<div class="js-summary-validation-result" data-js-summary-result>
Real-time validation summary passed. This account form can now be sent to your backend.
</div>
</form>
</div>
<aside class="js-summary-validation-sidebar">
<div>
<div class="js-summary-validation-kicker">Validation Summary</div>
<h3>See what is complete before submit</h3>
<p>This pattern gives users a clear overview of missing and completed fields.</p>
</div>
<div class="js-summary-validation-score-card">
<div class="js-summary-validation-score-row">
<span>Completion</span>
<strong data-js-summary-score>0%</strong>
</div>
<div class="js-summary-validation-progress">
<div class="js-summary-validation-progress-fill" data-js-summary-progress></div>
</div>
</div>
<div class="js-summary-validation-list">
<div class="js-summary-validation-item is-missing" data-summary-item="name">
<div class="js-summary-validation-item-icon"></div>
<div>
<strong>Name</strong>
<span data-summary-item-text="name">Missing</span>
</div>
</div>
<div class="js-summary-validation-item is-missing" data-summary-item="email">
<div class="js-summary-validation-item-icon"></div>
<div>
<strong>Email</strong>
<span data-summary-item-text="email">Missing</span>
</div>
</div>
<div class="js-summary-validation-item is-missing" data-summary-item="phone">
<div class="js-summary-validation-item-icon"></div>
<div>
<strong>Phone</strong>
<span data-summary-item-text="phone">Missing</span>
</div>
</div>
<div class="js-summary-validation-item is-missing" data-summary-item="website">
<div class="js-summary-validation-item-icon"></div>
<div>
<strong>Website</strong>
<span data-summary-item-text="website">Missing</span>
</div>
</div>
<div class="js-summary-validation-item is-missing" data-summary-item="companySize">
<div class="js-summary-validation-item-icon"></div>
<div>
<strong>Company size</strong>
<span data-summary-item-text="companySize">Missing</span>
</div>
</div>
<div class="js-summary-validation-item is-missing" data-summary-item="consent">
<div class="js-summary-validation-item-icon"></div>
<div>
<strong>Consent</strong>
<span data-summary-item-text="consent">Missing</span>
</div>
</div>
</div>
<div class="js-summary-validation-ready" data-js-summary-ready>
Complete all required fields to unlock submit.
</div>
</aside>
</div>
CSS
.js-summary-validation-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 370px;
gap: 26px;
align-items: stretch;
min-height: 700px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(236, 72, 153, 0.14), transparent 34%),
linear-gradient(135deg, #eef2ff 0%, #ffffff 52%, #fdf2f8 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-summary-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-summary-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-summary-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-summary-validation-form {
display: grid;
gap: 16px;
}
.js-summary-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-summary-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-summary-validation-field {
display: grid;
gap: 7px;
}
.js-summary-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-summary-validation-field input,
.js-summary-validation-field select {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-summary-validation-field input:focus,
.js-summary-validation-field select:focus {
background: #ffffff;
border-color: rgba(99, 102, 241, 0.62);
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.11);
}
.js-summary-validation-field.is-error input,
.js-summary-validation-field.is-error select {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-summary-validation-field.is-valid input,
.js-summary-validation-field.is-valid select {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-summary-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-summary-validation-field.is-valid .js-summary-validation-message {
color: #15803d;
}
.js-summary-validation-consent {
display: grid;
grid-template-columns: 22px 1fr;
gap: 12px;
padding: 15px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-summary-validation-consent input {
width: 19px;
height: 19px;
margin-top: 2px;
accent-color: #6366f1;
}
.js-summary-validation-consent label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-summary-validation-consent-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-summary-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #6366f1, #ec4899);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(99, 102, 241, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease, opacity 0.2s ease;
}
.js-summary-validation-submit:disabled {
opacity: 0.54;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.js-summary-validation-submit:not(:disabled):hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(99, 102, 241, 0.33);
}
.js-summary-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-summary-validation-result.is-visible {
display: block;
}
.js-summary-validation-sidebar {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #312e81 0%, #6366f1 48%, #db2777 100%);
box-shadow: 0 26px 80px rgba(99, 102, 241, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-summary-validation-sidebar::before {
content: "";
position: absolute;
right: -120px;
top: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-summary-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #e0e7ff;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-summary-validation-sidebar h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-summary-validation-sidebar p {
position: relative;
z-index: 2;
margin: 0;
color: #e0e7ff;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-summary-validation-score-card {
position: relative;
z-index: 2;
display: grid;
gap: 14px;
padding: 20px;
border-radius: 26px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
}
.js-summary-validation-score-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.js-summary-validation-score-row span {
color: #e0e7ff;
font-size: 12px;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-summary-validation-score-row strong {
color: #ffffff;
font-size: 28px;
line-height: 1;
font-weight: 950;
letter-spacing: -0.05em;
}
.js-summary-validation-progress {
height: 10px;
overflow: hidden;
border-radius: 999px;
background: rgba(255, 255, 255, 0.18);
}
.js-summary-validation-progress-fill {
width: 0%;
height: 100%;
border-radius: 999px;
background: #ffffff;
transition: width 0.25s ease;
}
.js-summary-validation-list {
position: relative;
z-index: 2;
display: grid;
gap: 10px;
}
.js-summary-validation-item {
display: grid;
grid-template-columns: 34px 1fr;
gap: 10px;
align-items: center;
padding: 12px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.js-summary-validation-item-icon {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 13px;
background: rgba(255, 255, 255, 0.16);
color: #ffffff;
font-size: 13px;
font-weight: 950;
}
.js-summary-validation-item.is-valid .js-summary-validation-item-icon {
background: #dcfce7;
color: #15803d;
}
.js-summary-validation-item.is-valid .js-summary-validation-item-icon::before {
content: "✓";
}
.js-summary-validation-item.is-missing .js-summary-validation-item-icon::before {
content: "!";
}
.js-summary-validation-item strong {
display: block;
margin-bottom: 2px;
color: #ffffff;
font-size: 13px;
line-height: 1.2;
font-weight: 950;
}
.js-summary-validation-item span {
display: block;
color: #e0e7ff;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
.js-summary-validation-ready {
position: relative;
z-index: 2;
padding: 15px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.14);
color: #e0e7ff;
font-size: 14px;
line-height: 1.55;
font-weight: 850;
}
@media (max-width: 940px) {
.js-summary-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-summary-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-summary-validation-card,
.js-summary-validation-sidebar {
padding: 24px;
border-radius: 24px;
}
.js-summary-validation-grid {
grid-template-columns: 1fr;
}
.js-summary-validation-grid .is-full {
grid-column: auto;
}
.js-summary-validation-card h3,
.js-summary-validation-sidebar h3 {
font-size: 30px;
}
}
This real-time validation summary example is useful for onboarding forms, account creation forms, checkout forms, lead generation forms, business profile forms, application forms, and any form where users benefit from seeing progress before submission.
28. Password Reset Validation
Password reset validation helps users create a new password safely after clicking a reset link. It should check password strength, confirm that both passwords match, validate the email address, and prevent weak passwords before submission.
This example validates email, new password, confirm password, password strength rules, and required security confirmation. It includes live rule checks, strength score, match status, and clear success feedback.
- Validates email and password reset fields
- Checks password length, uppercase, lowercase, number, and symbol rules
- Confirms that both passwords match
- Shows live password strength score
- Good for login systems, account recovery, SaaS dashboards, and membership websites
Reset password
Enter your email and choose a new password that passes all security rules.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-reset-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-reset-validation-form]");
const result = demo.querySelector("[data-js-reset-result]");
const strengthLabel = demo.querySelector("[data-js-reset-strength-label]");
const strengthFill = demo.querySelector("[data-js-reset-strength-fill]");
const statusTitle = demo.querySelector("[data-js-reset-status-title]");
const statusText = demo.querySelector("[data-js-reset-status-text]");
const toggleButtons = demo.querySelectorAll("[data-js-reset-toggle]");
const fields = {
email: form.querySelector("[name='email']"),
password: form.querySelector("[name='password']"),
confirmPassword: form.querySelector("[name='confirmPassword']"),
securityConfirm: form.querySelector("[name='securityConfirm']")
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-reset-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-reset-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
}
function getPasswordRules(value) {
return {
length: value.length >= 10,
uppercase: /[A-Z]/.test(value),
lowercase: /[a-z]/.test(value),
number: /\d/.test(value),
symbol: /[^A-Za-z0-9]/.test(value)
};
}
function updateRules() {
const value = fields.password.value;
const rules = getPasswordRules(value);
const ruleKeys = Object.keys(rules);
const passedCount = ruleKeys.filter(function (key) {
return rules[key];
}).length;
const percent = Math.round((passedCount / ruleKeys.length) * 100);
ruleKeys.forEach(function (key) {
const ruleItem = demo.querySelector("[data-reset-rule='" + key + "']");
ruleItem.classList.toggle("is-valid", rules[key]);
});
strengthLabel.textContent = percent + "%";
strengthFill.style.width = percent + "%";
if (percent === 0) {
statusTitle.textContent = "Security check";
statusText.textContent = "Complete the password rules to continue.";
} else if (percent < 60) {
statusTitle.textContent = "Weak password";
statusText.textContent = "Add more password requirements.";
} else if (percent < 100) {
statusTitle.textContent = "Almost there";
statusText.textContent = "A few password rules are still missing.";
} else {
statusTitle.textContent = "Strong password";
statusText.textContent = "All password strength rules passed.";
}
return percent === 100;
}
function validateEmail() {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
setField("email", false, "Enter a valid account email.");
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validatePassword() {
const isStrong = updateRules();
if (!fields.password.value) {
setField("password", false, "Enter a new password.");
return false;
}
if (!isStrong) {
setField("password", false, "Password must pass all strength rules.");
return false;
}
setField("password", true, "Password strength looks good.");
return true;
}
function validateConfirmPassword() {
if (!fields.confirmPassword.value) {
setField("confirmPassword", false, "Repeat the new password.");
return false;
}
if (fields.password.value !== fields.confirmPassword.value) {
setField("confirmPassword", false, "Passwords do not match.");
return false;
}
setField("confirmPassword", true, "Passwords match.");
return true;
}
function validateSecurityConfirm() {
const messageBox = demo.querySelector("[data-reset-message='securityConfirm']");
if (!fields.securityConfirm.checked) {
messageBox.textContent = "Please confirm this password change.";
messageBox.style.color = "#dc2626";
return false;
}
messageBox.textContent = "Password change confirmation accepted.";
messageBox.style.color = "#15803d";
return true;
}
function validateAll() {
return validateEmail() &&
validatePassword() &&
validateConfirmPassword() &&
validateSecurityConfirm();
}
toggleButtons.forEach(function (button) {
button.addEventListener("click", function () {
const fieldName = button.dataset.jsResetToggle;
const input = fields[fieldName];
if (input.type === "password") {
input.type = "text";
button.textContent = "Hide";
} else {
input.type = "password";
button.textContent = "Show";
}
});
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.password.addEventListener("input", function () {
validatePassword();
if (fields.confirmPassword.value) {
validateConfirmPassword();
}
result.classList.remove("is-visible");
});
fields.confirmPassword.addEventListener("input", function () {
validateConfirmPassword();
result.classList.remove("is-visible");
});
fields.securityConfirm.addEventListener("change", function () {
validateSecurityConfirm();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updateRules();
});
})();
HTML
<div class="js-reset-validation-preview" data-js-reset-validation-demo>
<aside class="js-reset-validation-security">
<div>
<div class="js-reset-validation-kicker">Password Reset</div>
<h3>Create a stronger replacement password</h3>
<p>Validate password strength and confirmation before users update account access.</p>
</div>
<div class="js-reset-validation-lock">
<div>
<div class="js-reset-validation-lock-icon">⌁</div>
<strong data-js-reset-status-title>Security check</strong>
<span data-js-reset-status-text>Complete the password rules to continue.</span>
</div>
</div>
<div class="js-reset-validation-strength-card">
<div class="js-reset-validation-strength-head">
<span>Strength</span>
<strong data-js-reset-strength-label>0%</strong>
</div>
<div class="js-reset-validation-strength-track">
<div class="js-reset-validation-strength-fill" data-js-reset-strength-fill></div>
</div>
</div>
</aside>
<div class="js-reset-validation-card">
<h3>Reset password</h3>
<p class="js-reset-validation-text">Enter your email and choose a new password that passes all security rules.</p>
<form class="js-reset-validation-form" novalidate data-js-reset-validation-form>
<div class="js-reset-validation-field" data-reset-field="email">
<label for="reset-validation-email">Account email</label>
<input id="reset-validation-email" name="email" type="email" placeholder="you@example.com">
<div class="js-reset-validation-message" data-reset-message="email"></div>
</div>
<div class="js-reset-validation-field" data-reset-field="password">
<label for="reset-validation-password">New password</label>
<div class="js-reset-validation-input-wrap">
<input id="reset-validation-password" name="password" type="password" placeholder="Create new password">
<button class="js-reset-validation-toggle" type="button" data-js-reset-toggle="password">Show</button>
</div>
<div class="js-reset-validation-message" data-reset-message="password"></div>
</div>
<div class="js-reset-validation-rules">
<div class="js-reset-validation-rule" data-reset-rule="length">At least 10 characters</div>
<div class="js-reset-validation-rule" data-reset-rule="uppercase">At least one uppercase letter</div>
<div class="js-reset-validation-rule" data-reset-rule="lowercase">At least one lowercase letter</div>
<div class="js-reset-validation-rule" data-reset-rule="number">At least one number</div>
<div class="js-reset-validation-rule" data-reset-rule="symbol">At least one symbol</div>
</div>
<div class="js-reset-validation-field" data-reset-field="confirmPassword">
<label for="reset-validation-confirm-password">Confirm new password</label>
<div class="js-reset-validation-input-wrap">
<input id="reset-validation-confirm-password" name="confirmPassword" type="password" placeholder="Repeat new password">
<button class="js-reset-validation-toggle" type="button" data-js-reset-toggle="confirmPassword">Show</button>
</div>
<div class="js-reset-validation-message" data-reset-message="confirmPassword"></div>
</div>
<div class="js-reset-validation-confirm">
<input id="reset-validation-confirm" name="securityConfirm" type="checkbox">
<label for="reset-validation-confirm">I understand this will replace my previous password.</label>
<div class="js-reset-validation-confirm-message" data-reset-message="securityConfirm"></div>
</div>
<button class="js-reset-validation-submit" type="submit">Validate Password Reset</button>
<div class="js-reset-validation-result" data-js-reset-result>
Password reset validation passed. This form can now update the account password securely.
</div>
</form>
</div>
</div>
CSS
.js-reset-validation-preview {
display: grid;
grid-template-columns: 360px minmax(0, 1fr);
gap: 26px;
align-items: stretch;
min-height: 690px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(239, 68, 68, 0.14), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(99, 102, 241, 0.16), transparent 34%),
linear-gradient(135deg, #fff1f2 0%, #ffffff 52%, #eef2ff 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-reset-validation-security {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #7f1d1d 0%, #dc2626 42%, #4f46e5 100%);
box-shadow: 0 26px 80px rgba(220, 38, 38, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-reset-validation-security::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 290px;
height: 290px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-reset-validation-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #fee2e2;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-reset-validation-security h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-reset-validation-security p {
position: relative;
z-index: 2;
margin: 0;
color: #fee2e2;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-reset-validation-lock {
position: relative;
z-index: 2;
display: grid;
place-items: center;
min-height: 220px;
padding: 24px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
text-align: center;
}
.js-reset-validation-lock-icon {
display: grid;
place-items: center;
width: 112px;
height: 112px;
margin-bottom: 14px;
border-radius: 38px;
background: rgba(255, 255, 255, 0.18);
color: #ffffff;
font-size: 48px;
line-height: 1;
box-shadow: 0 28px 70px rgba(0, 0, 0, 0.18);
}
.js-reset-validation-lock strong {
display: block;
color: #ffffff;
font-size: 20px;
line-height: 1.2;
font-weight: 950;
}
.js-reset-validation-lock span {
display: block;
margin-top: 6px;
color: #fee2e2;
font-size: 13px;
line-height: 1.4;
font-weight: 750;
}
.js-reset-validation-strength-card {
position: relative;
z-index: 2;
display: grid;
gap: 14px;
padding: 18px;
border-radius: 24px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.15);
}
.js-reset-validation-strength-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.js-reset-validation-strength-head span {
color: #fee2e2;
font-size: 12px;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-reset-validation-strength-head strong {
color: #ffffff;
font-size: 24px;
line-height: 1;
font-weight: 950;
}
.js-reset-validation-strength-track {
height: 10px;
overflow: hidden;
border-radius: 999px;
background: rgba(255, 255, 255, 0.18);
}
.js-reset-validation-strength-fill {
width: 0%;
height: 100%;
border-radius: 999px;
background: #ffffff;
transition: width 0.25s ease;
}
.js-reset-validation-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-reset-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-reset-validation-text {
margin: 0 0 24px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-reset-validation-form {
display: grid;
gap: 16px;
}
.js-reset-validation-field {
display: grid;
gap: 7px;
}
.js-reset-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-reset-validation-input-wrap {
position: relative;
}
.js-reset-validation-field input[type="email"],
.js-reset-validation-field input[type="password"],
.js-reset-validation-field input[type="text"] {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 52px 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-reset-validation-field input:focus {
background: #ffffff;
border-color: rgba(220, 38, 38, 0.62);
box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.11);
}
.js-reset-validation-field.is-error input {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-reset-validation-field.is-valid input {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-reset-validation-toggle {
position: absolute;
top: 50%;
right: 9px;
transform: translateY(-50%);
display: grid;
place-items: center;
width: 38px;
height: 38px;
border: 0;
border-radius: 14px;
background: #e2e8f0;
color: #334155;
font-size: 12px;
font-weight: 950;
cursor: pointer;
}
.js-reset-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-reset-validation-field.is-valid .js-reset-validation-message {
color: #15803d;
}
.js-reset-validation-rules {
display: grid;
gap: 9px;
padding: 16px;
border-radius: 22px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-reset-validation-rule {
display: flex;
align-items: center;
gap: 10px;
color: #64748b;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-reset-validation-rule::before {
content: "!";
display: grid;
place-items: center;
width: 22px;
height: 22px;
border-radius: 999px;
background: #fee2e2;
color: #dc2626;
font-size: 12px;
font-weight: 950;
}
.js-reset-validation-rule.is-valid {
color: #15803d;
}
.js-reset-validation-rule.is-valid::before {
content: "✓";
background: #dcfce7;
color: #15803d;
}
.js-reset-validation-confirm {
display: grid;
grid-template-columns: 22px 1fr;
gap: 12px;
padding: 15px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-reset-validation-confirm input {
width: 19px;
height: 19px;
margin-top: 2px;
accent-color: #dc2626;
}
.js-reset-validation-confirm label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-reset-validation-confirm-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-reset-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #dc2626, #4f46e5);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(220, 38, 38, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-reset-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(220, 38, 38, 0.33);
}
.js-reset-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-reset-validation-result.is-visible {
display: block;
}
@media (max-width: 940px) {
.js-reset-validation-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-reset-validation-preview {
padding: 18px;
border-radius: 24px;
}
.js-reset-validation-security,
.js-reset-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-reset-validation-card h3,
.js-reset-validation-security h3 {
font-size: 30px;
}
}
This password reset validation example is useful for account recovery pages, login systems, SaaS dashboards, membership websites, customer portals, admin panels, and any user account flow where a secure new password must be validated before submission.
29. Accessible Error Summary Validation
Accessible error summary validation helps users understand form problems quickly, especially when a form has multiple fields. Instead of only showing small inline errors, this pattern creates a clear error summary at the top of the form and links each error to the correct field.
This example validates name, email, topic, priority, message, and consent. When validation fails, an accessible error summary appears with clickable links that move focus to the relevant field.
- Shows an accessible error summary above the form
- Links each error to the matching input field
- Uses inline field validation messages
- Improves usability for long and important forms
- Good for contact forms, support forms, application forms, and checkout forms
Support request
Submit a complete support request. If anything is missing, the error summary links directly to the field.
Please fix the following errors:
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-accessible-summary-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-accessible-summary-form]");
const errorSummary = demo.querySelector("[data-js-error-summary]");
const errorList = demo.querySelector("[data-js-error-list]");
const result = demo.querySelector("[data-js-accessible-result]");
const errorCount = demo.querySelector("[data-js-accessible-error-count]");
const errorLabel = demo.querySelector("[data-js-accessible-error-label]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
topic: form.querySelector("[name='topic']"),
priority: form.querySelector("[name='priority']"),
message: form.querySelector("[name='message']"),
consent: form.querySelector("[name='consent']")
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-accessible-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-accessible-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
const input = fields[fieldName];
if (input) {
input.setAttribute("aria-invalid", isValid ? "false" : "true");
}
}
function validateName(errors) {
if (fields.name.value.trim().length < 2) {
const message = "Enter your full name.";
setField("name", false, message);
errors.push({ field: "name", id: "accessible-summary-name", message: message });
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail(errors) {
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(fields.email.value.trim())) {
const message = "Enter a valid email address.";
setField("email", false, message);
errors.push({ field: "email", id: "accessible-summary-email", message: message });
return false;
}
setField("email", true, "Email looks valid.");
return true;
}
function validateTopic(errors) {
if (!fields.topic.value) {
const message = "Choose a support topic.";
setField("topic", false, message);
errors.push({ field: "topic", id: "accessible-summary-topic", message: message });
return false;
}
setField("topic", true, "Support topic selected.");
return true;
}
function validatePriority(errors) {
if (!fields.priority.value) {
const message = "Choose a priority level.";
setField("priority", false, message);
errors.push({ field: "priority", id: "accessible-summary-priority", message: message });
return false;
}
setField("priority", true, "Priority selected.");
return true;
}
function validateMessage(errors) {
if (fields.message.value.trim().length < 30) {
const message = "Write at least 30 characters in the message.";
setField("message", false, message);
errors.push({ field: "message", id: "accessible-summary-message", message: message });
return false;
}
setField("message", true, "Message looks detailed enough.");
return true;
}
function validateConsent(errors) {
const messageBox = demo.querySelector("[data-accessible-message='consent']");
if (!fields.consent.checked) {
const message = "Confirm that the support request information is correct.";
messageBox.textContent = message;
messageBox.style.color = "#dc2626";
fields.consent.setAttribute("aria-invalid", "true");
errors.push({ field: "consent", id: "accessible-summary-consent", message: message });
return false;
}
messageBox.textContent = "Confirmation accepted.";
messageBox.style.color = "#15803d";
fields.consent.setAttribute("aria-invalid", "false");
return true;
}
function renderErrorSummary(errors) {
errorList.innerHTML = "";
errors.forEach(function (error) {
const item = document.createElement("li");
const link = document.createElement("a");
link.href = "#" + error.id;
link.textContent = error.message;
link.addEventListener("click", function (event) {
event.preventDefault();
const target = document.getElementById(error.id);
if (target) {
target.focus();
target.scrollIntoView({ behavior: "smooth", block: "center" });
}
});
item.appendChild(link);
errorList.appendChild(item);
});
errorSummary.classList.toggle("is-visible", errors.length > 0);
errorCount.textContent = String(errors.length);
errorLabel.textContent = errors.length === 1 ? "error found" : "errors found";
if (errors.length > 0) {
errorSummary.focus();
}
}
function validateAll() {
const errors = [];
validateName(errors);
validateEmail(errors);
validateTopic(errors);
validatePriority(errors);
validateMessage(errors);
validateConsent(errors);
renderErrorSummary(errors);
return errors.length === 0;
}
fields.name.addEventListener("input", function () {
const errors = [];
validateName(errors);
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
const errors = [];
validateEmail(errors);
result.classList.remove("is-visible");
});
fields.topic.addEventListener("change", function () {
const errors = [];
validateTopic(errors);
result.classList.remove("is-visible");
});
fields.priority.addEventListener("change", function () {
const errors = [];
validatePriority(errors);
result.classList.remove("is-visible");
});
fields.message.addEventListener("input", function () {
const errors = [];
validateMessage(errors);
result.classList.remove("is-visible");
});
fields.consent.addEventListener("change", function () {
const errors = [];
validateConsent(errors);
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
errorSummary.classList.remove("is-visible");
} else {
result.classList.remove("is-visible");
}
});
errorCount.textContent = "0";
errorLabel.textContent = "errors found";
});
})();
HTML
<div class="js-accessible-summary-preview" data-js-accessible-summary-demo>
<div class="js-accessible-summary-card">
<h3>Support request</h3>
<p class="js-accessible-summary-text">Submit a complete support request. If anything is missing, the error summary links directly to the field.</p>
<div class="js-accessible-summary-error-box" data-js-error-summary role="alert" aria-live="assertive" tabindex="-1">
<h4>Please fix the following errors:</h4>
<ul data-js-error-list></ul>
</div>
<form class="js-accessible-summary-form" novalidate data-js-accessible-summary-form>
<div class="js-accessible-summary-grid">
<div class="js-accessible-summary-field" data-accessible-field="name">
<label for="accessible-summary-name">Full name</label>
<input id="accessible-summary-name" name="name" type="text" placeholder="Your name" aria-describedby="accessible-summary-name-message">
<div id="accessible-summary-name-message" class="js-accessible-summary-message" data-accessible-message="name"></div>
</div>
<div class="js-accessible-summary-field" data-accessible-field="email">
<label for="accessible-summary-email">Email address</label>
<input id="accessible-summary-email" name="email" type="email" placeholder="you@example.com" aria-describedby="accessible-summary-email-message">
<div id="accessible-summary-email-message" class="js-accessible-summary-message" data-accessible-message="email"></div>
</div>
<div class="js-accessible-summary-field" data-accessible-field="topic">
<label for="accessible-summary-topic">Support topic</label>
<select id="accessible-summary-topic" name="topic" aria-describedby="accessible-summary-topic-message">
<option value="">Choose topic</option>
<option value="Billing">Billing</option>
<option value="Technical issue">Technical issue</option>
<option value="Account access">Account access</option>
<option value="Feature question">Feature question</option>
</select>
<div id="accessible-summary-topic-message" class="js-accessible-summary-message" data-accessible-message="topic"></div>
</div>
<div class="js-accessible-summary-field" data-accessible-field="priority">
<label for="accessible-summary-priority">Priority</label>
<select id="accessible-summary-priority" name="priority" aria-describedby="accessible-summary-priority-message">
<option value="">Choose priority</option>
<option value="Low">Low</option>
<option value="Normal">Normal</option>
<option value="Urgent">Urgent</option>
</select>
<div id="accessible-summary-priority-message" class="js-accessible-summary-message" data-accessible-message="priority"></div>
</div>
<div class="js-accessible-summary-field is-full" data-accessible-field="message">
<label for="accessible-summary-message">Message</label>
<textarea id="accessible-summary-message" name="message" placeholder="Describe the problem in at least 30 characters..." aria-describedby="accessible-summary-message-message"></textarea>
<div id="accessible-summary-message-message" class="js-accessible-summary-message" data-accessible-message="message"></div>
</div>
</div>
<div class="js-accessible-summary-consent">
<input id="accessible-summary-consent" name="consent" type="checkbox" aria-describedby="accessible-summary-consent-message">
<label for="accessible-summary-consent">I confirm that the support request information is correct.</label>
<div id="accessible-summary-consent-message" class="js-accessible-summary-consent-message" data-accessible-message="consent"></div>
</div>
<button class="js-accessible-summary-submit" type="submit">Validate Request</button>
<div class="js-accessible-summary-result" data-js-accessible-result>
Accessible validation passed. This support request can now be sent to your backend.
</div>
</form>
</div>
<aside class="js-accessible-summary-side">
<div>
<div class="js-accessible-summary-kicker">Accessible UX</div>
<h3>Make validation easier to understand</h3>
<p>Error summaries help users fix form problems faster, especially in longer forms.</p>
</div>
<div class="js-accessible-summary-metric">
<div>
<strong data-js-accessible-error-count>0</strong>
<span data-js-accessible-error-label>errors found</span>
</div>
</div>
<div class="js-accessible-summary-benefits">
<div class="js-accessible-summary-benefit">
<div class="js-accessible-summary-benefit-icon">↧</div>
<div>
<strong>Jump to field</strong>
<span>Each summary link focuses the correct input.</span>
</div>
</div>
<div class="js-accessible-summary-benefit">
<div class="js-accessible-summary-benefit-icon">!</div>
<div>
<strong>Clear overview</strong>
<span>Users see all problems in one place.</span>
</div>
</div>
</div>
</aside>
</div>
CSS
.js-accessible-summary-preview {
display: grid;
grid-template-columns: minmax(0, 1fr) 350px;
gap: 26px;
align-items: stretch;
min-height: 700px;
padding: 34px;
border-radius: 28px;
background:
radial-gradient(circle at 12% 16%, rgba(14, 165, 233, 0.16), transparent 34%),
radial-gradient(circle at 88% 18%, rgba(249, 115, 22, 0.14), transparent 34%),
linear-gradient(135deg, #f0f9ff 0%, #ffffff 52%, #fff7ed 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-accessible-summary-card {
padding: 32px;
border-radius: 32px;
background: #ffffff;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 26px 80px rgba(15, 23, 42, 0.13);
}
.js-accessible-summary-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 36px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-accessible-summary-text {
margin: 0 0 22px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-accessible-summary-error-box {
display: none;
margin-bottom: 18px;
padding: 18px;
border-radius: 22px;
background: #fff1f2;
border: 1px solid rgba(239, 68, 68, 0.25);
}
.js-accessible-summary-error-box.is-visible {
display: block;
}
.js-accessible-summary-error-box h4 {
margin: 0 0 10px;
color: #991b1b;
font-size: 16px;
line-height: 1.25;
font-weight: 950;
}
.js-accessible-summary-error-box ul {
margin: 0;
padding-left: 20px;
display: grid;
gap: 6px;
}
.js-accessible-summary-error-box a {
color: #b91c1c;
font-size: 14px;
line-height: 1.45;
font-weight: 850;
text-decoration: underline;
text-underline-offset: 3px;
}
.js-accessible-summary-form {
display: grid;
gap: 16px;
}
.js-accessible-summary-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.js-accessible-summary-grid .is-full {
grid-column: 1 / -1;
}
.js-accessible-summary-field {
display: grid;
gap: 7px;
}
.js-accessible-summary-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-accessible-summary-field input,
.js-accessible-summary-field select,
.js-accessible-summary-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 54px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-accessible-summary-field textarea {
min-height: 125px;
resize: vertical;
}
.js-accessible-summary-field input:focus,
.js-accessible-summary-field select:focus,
.js-accessible-summary-field textarea:focus {
background: #ffffff;
border-color: rgba(14, 165, 233, 0.62);
box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.11);
}
.js-accessible-summary-field.is-error input,
.js-accessible-summary-field.is-error select,
.js-accessible-summary-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-accessible-summary-field.is-valid input,
.js-accessible-summary-field.is-valid select,
.js-accessible-summary-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-accessible-summary-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-accessible-summary-field.is-valid .js-accessible-summary-message {
color: #15803d;
}
.js-accessible-summary-consent {
display: grid;
grid-template-columns: 22px 1fr;
gap: 12px;
padding: 15px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-accessible-summary-consent input {
width: 19px;
height: 19px;
margin-top: 2px;
accent-color: #0ea5e9;
}
.js-accessible-summary-consent label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-accessible-summary-consent-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-accessible-summary-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #0ea5e9, #f97316);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(14, 165, 233, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-accessible-summary-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(14, 165, 233, 0.33);
}
.js-accessible-summary-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-accessible-summary-result.is-visible {
display: block;
}
.js-accessible-summary-side {
position: relative;
overflow: hidden;
padding: 30px;
border-radius: 32px;
background:
radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.20), transparent 34%),
linear-gradient(135deg, #0c4a6e 0%, #0284c7 48%, #ea580c 100%);
box-shadow: 0 26px 80px rgba(14, 165, 233, 0.24);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 26px;
}
.js-accessible-summary-side::before {
content: "";
position: absolute;
right: -120px;
bottom: -120px;
width: 280px;
height: 280px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
}
.js-accessible-summary-kicker {
position: relative;
z-index: 2;
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #e0f2fe;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-accessible-summary-side h3 {
position: relative;
z-index: 2;
margin: 0 0 14px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
font-weight: 950;
letter-spacing: -0.075em;
}
.js-accessible-summary-side p {
position: relative;
z-index: 2;
margin: 0;
color: #e0f2fe;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-accessible-summary-metric {
position: relative;
z-index: 2;
display: grid;
place-items: center;
min-height: 220px;
padding: 24px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.16);
text-align: center;
}
.js-accessible-summary-metric strong {
display: block;
margin-bottom: 10px;
color: #ffffff;
font-size: 82px;
line-height: 0.95;
font-weight: 950;
letter-spacing: -0.08em;
}
.js-accessible-summary-metric span {
display: block;
color: #e0f2fe;
font-size: 14px;
line-height: 1.45;
font-weight: 850;
}
.js-accessible-summary-benefits {
position: relative;
z-index: 2;
display: grid;
gap: 12px;
}
.js-accessible-summary-benefit {
display: grid;
grid-template-columns: 42px 1fr;
gap: 12px;
align-items: center;
padding: 13px;
border-radius: 18px;
background: rgba(255, 255, 255, 0.13);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.js-accessible-summary-benefit-icon {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 15px;
background: rgba(255, 255, 255, 0.16);
color: #ffffff;
font-size: 18px;
}
.js-accessible-summary-benefit strong {
display: block;
margin-bottom: 3px;
color: #ffffff;
font-size: 13px;
line-height: 1.2;
font-weight: 950;
}
.js-accessible-summary-benefit span {
display: block;
color: #e0f2fe;
font-size: 12px;
line-height: 1.35;
font-weight: 700;
}
@media (max-width: 940px) {
.js-accessible-summary-preview {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-accessible-summary-preview {
padding: 18px;
border-radius: 24px;
}
.js-accessible-summary-card,
.js-accessible-summary-side {
padding: 24px;
border-radius: 24px;
}
.js-accessible-summary-grid {
grid-template-columns: 1fr;
}
.js-accessible-summary-grid .is-full {
grid-column: auto;
}
.js-accessible-summary-card h3,
.js-accessible-summary-side h3 {
font-size: 30px;
}
}
This accessible error summary validation example is useful for long contact forms, checkout forms, account registration, government-style forms, application forms, support forms, booking forms, and any flow where users need a clear overview of all validation errors.
30. Complete Responsive Form Validation Section
A complete responsive form validation section combines several practical validation patterns into one polished layout. It can be used as a starting point for a real landing page contact section, SaaS demo request, quote form, agency inquiry form, or client onboarding form.
This final example validates name, business email, company website, service interest, budget, timeline, project message, and consent. It includes a responsive two-column section, live validation, a project summary panel, and clear success feedback.
- Complete responsive validation section
- Validates text, email, URL, select, textarea, and checkbox fields
- Includes live project summary
- Works as a full landing page form block
- Good for SaaS demo forms, agency quote forms, service inquiries, and onboarding forms
Turn visitors into qualified project leads
Use this responsive JavaScript validation section for a real landing page, service page, SaaS demo page, or agency quote request flow.
Live project summary
Request a project quote
Tell us about your project. JavaScript validates the form before submission.
JavaScript
(function () {
const demos = document.querySelectorAll("[data-js-complete-validation-demo]");
demos.forEach(function (demo) {
const form = demo.querySelector("[data-js-complete-validation-form]");
const result = demo.querySelector("[data-js-complete-result]");
const fields = {
name: form.querySelector("[name='name']"),
email: form.querySelector("[name='email']"),
website: form.querySelector("[name='website']"),
service: form.querySelector("[name='service']"),
budget: form.querySelector("[name='budget']"),
timeline: form.querySelector("[name='timeline']"),
message: form.querySelector("[name='message']"),
consent: form.querySelector("[name='consent']")
};
const preview = {
service: demo.querySelector("[data-js-complete-preview-service]"),
budget: demo.querySelector("[data-js-complete-preview-budget]"),
timeline: demo.querySelector("[data-js-complete-preview-timeline]"),
website: demo.querySelector("[data-js-complete-preview-website]"),
validCount: demo.querySelector("[data-js-complete-valid-count]"),
progress: demo.querySelector("[data-js-complete-progress]")
};
const state = {
name: false,
email: false,
website: false,
service: false,
budget: false,
timeline: false,
message: false,
consent: false
};
function setField(fieldName, isValid, message) {
const wrap = demo.querySelector("[data-complete-field='" + fieldName + "']");
const messageBox = demo.querySelector("[data-complete-message='" + fieldName + "']");
if (wrap) {
wrap.classList.remove("is-valid", "is-error");
wrap.classList.add(isValid ? "is-valid" : "is-error");
}
if (messageBox) {
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
}
state[fieldName] = isValid;
updatePreview();
}
function setConsent(isValid, message) {
const messageBox = demo.querySelector("[data-complete-message='consent']");
messageBox.textContent = message;
messageBox.style.color = isValid ? "#15803d" : "#dc2626";
state.consent = isValid;
updatePreview();
}
function updatePreview() {
const keys = Object.keys(state);
const validCount = keys.filter(function (key) {
return state[key];
}).length;
const percent = Math.round((validCount / keys.length) * 100);
preview.service.textContent = fields.service.value || "Not selected yet";
preview.budget.textContent = fields.budget.value || "Not selected yet";
preview.timeline.textContent = fields.timeline.value || "Not selected yet";
preview.website.textContent = fields.website.value.trim() || "Not entered yet";
preview.validCount.textContent = String(validCount);
preview.progress.textContent = percent + "%";
}
function validateName() {
if (fields.name.value.trim().length < 2) {
setField("name", false, "Enter your full name.");
return false;
}
setField("name", true, "Name looks good.");
return true;
}
function validateEmail() {
const value = fields.email.value.trim();
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(value)) {
setField("email", false, "Enter a valid business email.");
return false;
}
setField("email", true, "Business email looks valid.");
return true;
}
function validateWebsite() {
try {
const url = new URL(fields.website.value.trim());
if (url.protocol !== "http:" && url.protocol !== "https:") {
setField("website", false, "Website URL must start with http or https.");
return false;
}
setField("website", true, "Website URL looks valid.");
return true;
} catch (error) {
setField("website", false, "Enter a valid website URL.");
return false;
}
}
function validateService() {
if (!fields.service.value) {
setField("service", false, "Choose a service interest.");
return false;
}
setField("service", true, "Service selected.");
return true;
}
function validateBudget() {
if (!fields.budget.value) {
setField("budget", false, "Choose a budget range.");
return false;
}
setField("budget", true, "Budget selected.");
return true;
}
function validateTimeline() {
if (!fields.timeline.value) {
setField("timeline", false, "Choose a project timeline.");
return false;
}
setField("timeline", true, "Timeline selected.");
return true;
}
function validateMessage() {
if (fields.message.value.trim().length < 35) {
setField("message", false, "Write at least 35 characters about the project.");
return false;
}
setField("message", true, "Project message looks detailed enough.");
return true;
}
function validateConsent() {
if (!fields.consent.checked) {
setConsent(false, "Please confirm this project request.");
return false;
}
setConsent(true, "Consent accepted.");
return true;
}
function validateAll() {
return validateName() &&
validateEmail() &&
validateWebsite() &&
validateService() &&
validateBudget() &&
validateTimeline() &&
validateMessage() &&
validateConsent();
}
fields.name.addEventListener("input", function () {
validateName();
result.classList.remove("is-visible");
});
fields.email.addEventListener("input", function () {
validateEmail();
result.classList.remove("is-visible");
});
fields.website.addEventListener("input", function () {
validateWebsite();
result.classList.remove("is-visible");
});
fields.service.addEventListener("change", function () {
validateService();
result.classList.remove("is-visible");
});
fields.budget.addEventListener("change", function () {
validateBudget();
result.classList.remove("is-visible");
});
fields.timeline.addEventListener("change", function () {
validateTimeline();
result.classList.remove("is-visible");
});
fields.message.addEventListener("input", function () {
validateMessage();
result.classList.remove("is-visible");
});
fields.consent.addEventListener("change", function () {
validateConsent();
result.classList.remove("is-visible");
});
form.addEventListener("submit", function (event) {
event.preventDefault();
if (validateAll()) {
result.classList.add("is-visible");
} else {
result.classList.remove("is-visible");
}
});
updatePreview();
});
})();
HTML
<section class="js-complete-validation-section" data-js-complete-validation-demo>
<div class="js-complete-validation-layout">
<div class="js-complete-validation-content">
<div>
<div class="js-complete-validation-kicker">Complete Validation Section</div>
<h3>Turn visitors into qualified project leads</h3>
<p>Use this responsive JavaScript validation section for a real landing page, service page, SaaS demo page, or agency quote request flow.</p>
</div>
<div class="js-complete-validation-summary">
<h4>Live project summary</h4>
<div class="js-complete-validation-summary-line">
<span>Service</span>
<strong data-js-complete-preview-service>Not selected yet</strong>
</div>
<div class="js-complete-validation-summary-line">
<span>Budget</span>
<strong data-js-complete-preview-budget>Not selected yet</strong>
</div>
<div class="js-complete-validation-summary-line">
<span>Timeline</span>
<strong data-js-complete-preview-timeline>Not selected yet</strong>
</div>
<div class="js-complete-validation-summary-line">
<span>Website</span>
<strong data-js-complete-preview-website>Not entered yet</strong>
</div>
</div>
<div class="js-complete-validation-stats">
<div class="js-complete-validation-stat">
<strong data-js-complete-valid-count>0</strong>
<span>valid fields</span>
</div>
<div class="js-complete-validation-stat">
<strong data-js-complete-progress>0%</strong>
<span>completion</span>
</div>
<div class="js-complete-validation-stat">
<strong>8</strong>
<span>total checks</span>
</div>
</div>
</div>
<div class="js-complete-validation-card">
<h3>Request a project quote</h3>
<p class="js-complete-validation-card-text">Tell us about your project. JavaScript validates the form before submission.</p>
<form class="js-complete-validation-form" novalidate data-js-complete-validation-form>
<div class="js-complete-validation-grid">
<div class="js-complete-validation-field" data-complete-field="name">
<label for="complete-validation-name">Full name</label>
<input id="complete-validation-name" name="name" type="text" placeholder="Your name">
<div class="js-complete-validation-message" data-complete-message="name"></div>
</div>
<div class="js-complete-validation-field" data-complete-field="email">
<label for="complete-validation-email">Business email</label>
<input id="complete-validation-email" name="email" type="email" placeholder="you@company.com">
<div class="js-complete-validation-message" data-complete-message="email"></div>
</div>
<div class="js-complete-validation-field is-full" data-complete-field="website">
<label for="complete-validation-website">Company website</label>
<input id="complete-validation-website" name="website" type="url" placeholder="https://example.com">
<div class="js-complete-validation-message" data-complete-message="website"></div>
</div>
<div class="js-complete-validation-field" data-complete-field="service">
<label for="complete-validation-service">Service interest</label>
<select id="complete-validation-service" name="service">
<option value="">Choose service</option>
<option value="JavaScript UI development">JavaScript UI development</option>
<option value="Website redesign">Website redesign</option>
<option value="WordPress development">WordPress development</option>
<option value="SaaS landing page">SaaS landing page</option>
</select>
<div class="js-complete-validation-message" data-complete-message="service"></div>
</div>
<div class="js-complete-validation-field" data-complete-field="budget">
<label for="complete-validation-budget">Budget range</label>
<select id="complete-validation-budget" name="budget">
<option value="">Choose budget</option>
<option value="$500-$1500">$500-$1500</option>
<option value="$1500-$5000">$1500-$5000</option>
<option value="$5000-$10000">$5000-$10000</option>
<option value="$10000+">$10000+</option>
</select>
<div class="js-complete-validation-message" data-complete-message="budget"></div>
</div>
<div class="js-complete-validation-field is-full" data-complete-field="timeline">
<label for="complete-validation-timeline">Timeline</label>
<select id="complete-validation-timeline" name="timeline">
<option value="">Choose timeline</option>
<option value="ASAP">ASAP</option>
<option value="2-4 weeks">2-4 weeks</option>
<option value="1-2 months">1-2 months</option>
<option value="Flexible">Flexible</option>
</select>
<div class="js-complete-validation-message" data-complete-message="timeline"></div>
</div>
<div class="js-complete-validation-field is-full" data-complete-field="message">
<label for="complete-validation-message">Project message</label>
<textarea id="complete-validation-message" name="message" placeholder="Describe your goals, pages, features, or current problem..."></textarea>
<div class="js-complete-validation-message" data-complete-message="message"></div>
</div>
</div>
<div class="js-complete-validation-consent">
<input id="complete-validation-consent" name="consent" type="checkbox">
<label for="complete-validation-consent">I confirm that the information is correct and agree to be contacted about this project request.</label>
<div class="js-complete-validation-consent-message" data-complete-message="consent"></div>
</div>
<button class="js-complete-validation-submit" type="submit">Validate Complete Form</button>
<div class="js-complete-validation-result" data-js-complete-result>
Complete responsive form validation passed. This section is ready for a real backend, CRM, or email handler.
</div>
</form>
</div>
</div>
</section>
CSS
.js-complete-validation-section {
position: relative;
overflow: hidden;
min-height: 760px;
padding: 38px;
border-radius: 30px;
background:
radial-gradient(circle at 10% 10%, rgba(56, 189, 248, 0.18), transparent 30%),
radial-gradient(circle at 90% 18%, rgba(168, 85, 247, 0.18), transparent 34%),
radial-gradient(circle at 55% 95%, rgba(34, 197, 94, 0.14), transparent 36%),
linear-gradient(135deg, #020617 0%, #111827 48%, #172554 100%);
color: #ffffff;
}
.js-complete-validation-section::before {
content: "";
position: absolute;
inset: 18px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 24px;
pointer-events: none;
}
.js-complete-validation-layout {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
gap: 30px;
align-items: stretch;
}
.js-complete-validation-content {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 28px;
padding: 10px 0;
}
.js-complete-validation-kicker {
display: inline-flex;
width: fit-content;
margin-bottom: 18px;
padding: 8px 12px;
border-radius: 999px;
background: rgba(56, 189, 248, 0.14);
border: 1px solid rgba(125, 211, 252, 0.20);
color: #bae6fd;
font-size: 13px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-complete-validation-content h3 {
margin: 0 0 18px;
color: #ffffff !important;
-webkit-text-fill-color: #ffffff !important;
font-size: clamp(40px, 6vw, 72px);
line-height: 0.98;
font-weight: 950;
letter-spacing: -0.08em;
}
.js-complete-validation-content p {
max-width: 580px;
margin: 0;
color: #cbd5e1;
font-size: 16px;
line-height: 1.75;
font-weight: 650;
}
.js-complete-validation-summary {
display: grid;
gap: 13px;
padding: 22px;
border-radius: 28px;
background: rgba(255, 255, 255, 0.09);
border: 1px solid rgba(255, 255, 255, 0.12);
backdrop-filter: blur(18px);
}
.js-complete-validation-summary h4 {
margin: 0 0 4px;
color: #ffffff;
font-size: 18px;
line-height: 1.2;
font-weight: 950;
}
.js-complete-validation-summary-line {
display: grid;
gap: 4px;
padding: 12px;
border-radius: 18px;
background: rgba(15, 23, 42, 0.34);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.js-complete-validation-summary-line span {
color: #93c5fd;
font-size: 11px;
line-height: 1;
font-weight: 950;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.js-complete-validation-summary-line strong {
color: #ffffff;
font-size: 14px;
line-height: 1.35;
font-weight: 850;
overflow-wrap: anywhere;
}
.js-complete-validation-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.js-complete-validation-stat {
padding: 16px;
border-radius: 22px;
background: rgba(255, 255, 255, 0.09);
border: 1px solid rgba(255, 255, 255, 0.11);
}
.js-complete-validation-stat strong {
display: block;
margin-bottom: 5px;
color: #ffffff;
font-size: 26px;
line-height: 1;
font-weight: 950;
letter-spacing: -0.05em;
}
.js-complete-validation-stat span {
display: block;
color: #cbd5e1;
font-size: 12px;
line-height: 1.35;
font-weight: 750;
}
.js-complete-validation-card {
padding: 30px;
border-radius: 32px;
background: rgba(255, 255, 255, 0.96);
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow: 0 32px 90px rgba(0, 0, 0, 0.30);
color: #111827;
}
.js-complete-validation-card h3 {
margin: 0 0 10px;
color: #111827;
font-size: 34px;
line-height: 1.08;
font-weight: 950;
letter-spacing: -0.06em;
}
.js-complete-validation-card-text {
margin: 0 0 22px;
color: #64748b;
font-size: 15px;
line-height: 1.7;
font-weight: 650;
}
.js-complete-validation-form {
display: grid;
gap: 15px;
}
.js-complete-validation-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 13px;
}
.js-complete-validation-grid .is-full {
grid-column: 1 / -1;
}
.js-complete-validation-field {
display: grid;
gap: 7px;
}
.js-complete-validation-field label {
color: #111827;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.02em;
}
.js-complete-validation-field input,
.js-complete-validation-field select,
.js-complete-validation-field textarea {
width: 100%;
box-sizing: border-box;
min-height: 53px;
padding: 14px 15px;
border: 1px solid rgba(15, 23, 42, 0.13);
border-radius: 18px;
background: #f8fafc;
color: #111827;
font-size: 15px;
line-height: 1.4;
font-weight: 650;
outline: none;
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}
.js-complete-validation-field textarea {
min-height: 120px;
resize: vertical;
}
.js-complete-validation-field input:focus,
.js-complete-validation-field select:focus,
.js-complete-validation-field textarea:focus {
background: #ffffff;
border-color: rgba(59, 130, 246, 0.62);
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.11);
}
.js-complete-validation-field.is-error input,
.js-complete-validation-field.is-error select,
.js-complete-validation-field.is-error textarea {
border-color: rgba(239, 68, 68, 0.62);
background: #fff1f2;
}
.js-complete-validation-field.is-valid input,
.js-complete-validation-field.is-valid select,
.js-complete-validation-field.is-valid textarea {
border-color: rgba(34, 197, 94, 0.58);
background: #f0fdf4;
}
.js-complete-validation-message {
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-complete-validation-field.is-valid .js-complete-validation-message {
color: #15803d;
}
.js-complete-validation-consent {
display: grid;
grid-template-columns: 22px 1fr;
gap: 12px;
padding: 15px;
border-radius: 20px;
background: #f8fafc;
border: 1px solid rgba(15, 23, 42, 0.08);
}
.js-complete-validation-consent input {
width: 19px;
height: 19px;
margin-top: 2px;
accent-color: #2563eb;
}
.js-complete-validation-consent label {
color: #334155;
font-size: 14px;
line-height: 1.55;
font-weight: 750;
}
.js-complete-validation-consent-message {
grid-column: 1 / -1;
min-height: 18px;
color: #dc2626;
font-size: 13px;
line-height: 1.35;
font-weight: 800;
}
.js-complete-validation-submit {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 54px;
padding: 16px 22px;
border: 0;
border-radius: 999px;
background: linear-gradient(135deg, #2563eb, #7c3aed);
color: #ffffff;
font-size: 15px;
line-height: 1;
font-weight: 950;
cursor: pointer;
box-shadow: 0 18px 42px rgba(37, 99, 235, 0.24);
transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.js-complete-validation-submit:hover {
transform: translateY(-2px);
filter: saturate(1.08);
box-shadow: 0 22px 54px rgba(37, 99, 235, 0.33);
}
.js-complete-validation-result {
display: none;
padding: 14px 16px;
border-radius: 18px;
background: #ecfdf5;
border: 1px solid rgba(34, 197, 94, 0.22);
color: #166534;
font-size: 14px;
line-height: 1.5;
font-weight: 850;
}
.js-complete-validation-result.is-visible {
display: block;
}
@media (max-width: 980px) {
.js-complete-validation-layout {
grid-template-columns: 1fr;
}
}
@media (max-width: 680px) {
.js-complete-validation-section {
padding: 22px;
border-radius: 24px;
}
.js-complete-validation-card {
padding: 24px;
border-radius: 24px;
}
.js-complete-validation-grid,
.js-complete-validation-stats {
grid-template-columns: 1fr;
}
.js-complete-validation-grid .is-full {
grid-column: auto;
}
.js-complete-validation-content h3,
.js-complete-validation-card h3 {
font-size: 30px;
}
}
This complete responsive form validation section is useful for SaaS demo requests, landing page contact sections, agency quote forms, project inquiry pages, service request forms, client onboarding forms, and conversion-focused website sections that need polished JavaScript validation.
If you are adding JavaScript validation, interactive forms, modals, checkout flows, and conversion-focused UI sections to your website, reliable hosting helps keep the experience fast and smooth.
JavaScript Form Validation Best Practices
Good JavaScript form validation should make forms easier to complete, not more frustrating. The best validation patterns are clear, fast, accessible, mobile-friendly, and supported by server-side validation. A form should tell users what went wrong, where it happened, and how to fix it without making them guess.
Use JavaScript validation for instant feedback, inline messages, password strength indicators, live summaries, file checks, checkout previews, and better interaction. But for real forms, always repeat important validation on the server because front-end JavaScript can be disabled, modified, or bypassed.
Validate at the right moment
Do not show aggressive error messages before the user has started typing. Validate on input for simple fields, on blur for longer fields, and on submit for a final complete check.
Use clear error messages
Write messages that explain the fix. “Enter a valid email address” is much better than “Invalid input” because users immediately understand what to change.
Show success states carefully
Positive feedback can help users feel confident, but do not overload every field with unnecessary animations. Keep success messages calm and useful.
Use server-side validation too
Client-side validation improves user experience, but it is not security. Validate again on the backend for accounts, payments, file uploads, checkout, and private data.
For most website projects, the strongest validation system combines native HTML attributes, JavaScript checks, helpful UI feedback, and backend validation. Use required, type="email", minlength, maxlength, min, max, and accept where they make sense, then enhance the experience with JavaScript for better messages and live feedback.
- Keep labels visible so users know what each field means even after typing.
- Place errors near the field so users do not need to search for the problem.
- Use specific messages for missing fields, invalid emails, weak passwords, file type errors, and quantity limits.
- Do not rely only on red and green colors because some users may not see color differences clearly.
- Use accessible attributes such as labels,
aria-invalid,aria-describedby, and live regions when needed. - Do not block copy and paste in password or email fields unless there is a very strong reason.
- Test keyboard navigation so users can complete the form without a mouse.
- Validate again on submit even if you already validated while the user was typing.
If you are designing the visual side of forms, combine these validation patterns with our modern CSS forms guide. For stronger CTA buttons inside forms, use ideas from modern CSS buttons. If your validation form appears in a popup, see our JavaScript modal examples.
Responsive Validation Design Tips
Responsive form validation is more than making inputs fit on a small screen. On mobile devices, validation messages need enough spacing, buttons must be easy to tap, summaries should not cover important content, and form layouts should stack naturally. A beautiful desktop form can become hard to use if error messages, labels, and submit buttons are cramped on mobile.
Make every error easy to read and fix on small screens
On mobile, users need direct feedback near the field they are editing. Keep error text short, stack columns into one column, use large enough tap targets, and avoid sticky elements that hide inputs or submit buttons.
Stack form columns
Two-column desktop forms should usually become one-column forms on smaller screens.
Keep messages close
Error and success messages should appear directly below the field they describe.
Use large tap targets
Inputs, checkboxes, radio options, file upload zones, and buttons need enough height and spacing.
Avoid hidden errors
If a form has many fields, use a summary or scroll the first invalid field into view.
When you test responsive validation, try real mobile behavior: tap through fields, trigger errors, open the keyboard, upload a file, select dropdown options, use checkboxes, and submit an incomplete form. This helps you catch spacing problems, hidden messages, awkward scrolling, and validation summaries that may work on desktop but feel confusing on mobile.
- Use a single-column layout on mobile for most forms with multiple fields.
- Keep submit buttons visible and easy to tap without making them sticky in a way that blocks fields.
- Make checkbox and radio labels clickable so users do not need to tap tiny controls.
- Use short messages on mobile because long errors can push the next field too far down.
- Scroll to the first invalid field when a long form fails validation.
- Test file upload validation on mobile because camera and file picker behavior may vary.
For form sections that sit inside larger responsive page layouts, you can use ideas from modern CSS layouts, modern CSS navigation menus, modern CSS tabs, and modern CSS accordions.
Common Form Validation Mistakes
Form validation mistakes often happen when validation is written only as a technical check instead of a user experience feature. A form can be technically correct but still frustrating if the messages are vague, the errors appear too late, or the user cannot easily find what needs to be fixed.
Only validating after submit
If users fill a long form and only see errors after pressing submit, the experience can feel slow and annoying. Use inline feedback where it helps.
Using vague error messages
Messages like “Invalid” or “Error” do not explain the fix. Use specific text for email, password, phone, file, date, and required field problems.
Relying only on color
Red and green borders are useful, but they should not be the only signal. Add text, icons, labels, or summaries so the state is clear.
Forgetting server validation
JavaScript validation can be bypassed. Every important rule should also be checked on the server before saving or processing data.
Blocking useful input
Overly strict validation can reject valid names, phone formats, addresses, or international emails. Avoid rules that are stricter than necessary.
Bad mobile spacing
Error messages, dropdowns, checkboxes, password rules, and submit buttons need enough room on mobile screens.
A strong validation flow should feel helpful and calm. It should not punish users for typing, pasting, editing, or using a different input format. Keep validation flexible where possible, specific where necessary, and always clear enough that users know the next step.
JavaScript Form Validation FAQ
Here are common questions about JavaScript form validation, input validation, password rules, checkout validation, accessibility, and whether client-side validation is enough for real production forms.
What is JavaScript form validation?
JavaScript form validation checks user input in the browser before a form is submitted. It can validate required fields, email addresses, passwords, phone numbers, dates, file uploads, checkboxes, quantities, and other form values.
Is JavaScript validation enough for a real website form?
No. JavaScript validation improves user experience, but it is not enough for security. Real forms should also validate data on the server because users can disable JavaScript, modify front-end code, or send requests directly to the backend.
Should form validation happen while typing or after submit?
Use both carefully. Real-time validation is useful for email fields, password strength, character counters, and live summaries. Final submit validation is still needed to catch anything missing before the form is sent.
How do I show better validation error messages?
Place messages close to the field, use simple language, and explain the fix. For example, “Enter a valid email address” is clearer than “Invalid value”. For long forms, an error summary at the top can also help users find all issues quickly.
How can I validate an email address with JavaScript?
You can use a basic regular expression for obvious email mistakes, or combine native type="email" with custom JavaScript messages. Avoid overly strict patterns because many real email addresses are more varied than simple examples.
How do I validate password strength?
A common password validation pattern checks length, uppercase letters, lowercase letters, numbers, symbols, and whether the confirmation password matches. Always explain the rules clearly and avoid vague “weak password” messages without details.
How do I make form validation accessible?
Use real labels, readable messages, visible focus states, aria-invalid, aria-describedby, and error summaries when helpful. Do not rely only on color to show whether a field is valid or invalid.
Can I use these JavaScript validation examples in WordPress?
Yes. You can paste the Gutenberg blocks into a WordPress post or adapt the HTML, CSS, and JavaScript into a theme, child theme, custom plugin, shortcode, landing page, or form template. For production forms, connect the front-end validation to secure backend handling.
Conclusion
JavaScript form validation is one of the most practical ways to improve modern website forms. It helps users complete signup forms, login forms, contact forms, checkout forms, booking forms, quote request forms, newsletter forms, file upload forms, password reset forms, and account forms with fewer mistakes and clearer guidance.
The best validation systems combine helpful client-side feedback with secure server-side checks. JavaScript can show instant messages, highlight fields, calculate live totals, validate passwords, check files, confirm dates, and improve the user journey. The server should still make the final decision before data is saved, sent, processed, or used for payments and accounts.
A good validation experience is clear, calm, accessible, responsive, and specific. Start with the examples above, adjust the rules to match your real form, and always add backend validation before using the form in production.
Related JavaScript and CSS Guides
If you are building better website forms and interactive UI sections, these related guides work well together with JavaScript form validation. They help you design better form layouts, buttons, modals, tabs, accordions, pricing sections, navigation menus, and responsive page structures.