Form Validation with HTML, CSS & JavaScript
20 DAYS 20 PROJECT CHALLENGE
Day #03
Project Overview
A beginner-friendly Form Validation project built with HTML, CSS and JavaScript.
This project validates user input on a signup/login form before submission using regular expressions and DOM events. It performs live validation as the user types, enforces rules (required fields, email format, password rules, matching passwords), shows inline error messages, and prevents submission if validation fails. It demonstrates regular expressions, event handling, form validation patterns, and simple UX improvements (live feedback, toggles, success states).
Key Features
- Validates required fields (name, email, password).
- Email format validation using a regex.
- Password policy enforcement (minimum length, uppercase, lowercase, digit, special character).
- Confirm password matching.
- Live inline error messages and visual state (valid/invalid).
- Password strength meter based on length and variety.
- Prevents form submission if validation fails and shows a success summary on success.
- Accessible error messaging (ARIA-friendly).
- Minimal, responsive layout suitable for learning and extension.
HTML Code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Day 3 — Form Validation</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="card" role="main">
<header>
<div class="logo">FV</div>
<div>
<h1>Day 3: Form Validation</h1>
<p class="lead">Validate signup/login inputs with regex and DOM events. Real-time feedback, password policy, and accessible error messages.</p>
</div>
</header>
<section class="form-wrap">
<form id="signupForm" novalidate>
<div class="field">
<label for="name">Full name<span class="req">*</span></label>
<input id="name" name="name" type="text" autocomplete="name" required />
<div class="error" id="nameError" aria-live="polite"></div>
</div>
<div class="field">
<label for="email">Email<span class="req">*</span></label>
<input id="email" name="email" type="email" autocomplete="email" required />
<div class="error" id="emailError" aria-live="polite"></div>
</div>
<div class="field">
<label for="password">Password<span class="req">*</span></label>
<div class="pw-row">
<input id="password" name="password" type="password" autocomplete="new-password" required />
<button type="button" id="togglePwd" class="tiny-btn" aria-label="Show password">Show</button>
</div>
<div class="small">Minimum 8 chars, include uppercase, lowercase, number, and special character.</div>
<div class="strength" aria-hidden="true"><i id="pwBar"></i></div>
<div class="error" id="passwordError" aria-live="polite"></div>
</div>
<div class="field">
<label for="confirm">Confirm password<span class="req">*</span></label>
<input id="confirm" name="confirm" type="password" autocomplete="new-password" required />
<div class="error" id="confirmError" aria-live="polite"></div>
</div>
<div class="field">
<label for="phone">Phone (optional)</label>
<input id="phone" name="phone" type="tel" inputmode="tel" placeholder="+91 98765 43210" />
<div class="error" id="phoneError" aria-live="polite"></div>
</div>
<div class="field checkbox">
<label><input id="terms" name="terms" type="checkbox" /> I agree to the terms <span class="req">*</span></label>
<div class="error" id="termsError" aria-live="polite"></div>
</div>
<div class="actions">
<button type="submit" id="submitBtn" class="btn">Sign up</button>
<button type="button" id="resetBtn" class="btn secondary">Reset</button>
</div>
<div id="formMessage" class="form-message" role="status" aria-live="polite"></div>
</form>
</section>
<section class="notes">
<details>
<summary>How it works (short)</summary>
<p>The JS attaches input and submit listeners. It uses regex to validate email and password rules, updates inline error messages, updates a visual strength bar, and prevents submission until all checks pass.</p>
</details>
</section>
</main>
<script src="script.js"></script>
</body>
</html>
CSS Code
:root {
--bg: #0f1724;
--card: #071027;
--accent: #6366f1;
--muted: #9aa4b2;
--danger: #ef4444;
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: #002252;
color: #e6eef6;
display: flex;
align-items: center;
justify-content: center;
padding: 28px;
}
.card {
width: min(760px, 96%);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
border-radius: 12px;
padding: 20px;
box-shadow: 0 8px 30px rgba(2, 6, 23, 0.6);
border: 1px solid rgba(255, 255, 255, 0.03);
}
header {
display: flex;
gap: 14px;
align-items: center;
}
.logo {
width: 44px;
height: 44px;
border-radius: 10px;
background: linear-gradient(135deg, var(--accent), #22c1c3);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
}
h1 {
margin: 0;
font-size: 18px;
}
.lead {
margin: 4px 0 12px;
color: var(--muted);
font-size: 14px;
}
.form-wrap {
margin-top: 16px;
}
form {
display: grid;
gap: 12px;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.field input[type="text"],
.field input[type="email"],
.field input[type="password"],
.field input[type="tel"] {
padding: 10px 12px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.04);
background: #ffff;
color: black;
outline: none;
}
.field input:focus {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.12);
border-color: rgba(99, 102, 241, 0.6);
}
.req {
color: var(--muted);
margin-left: 4px;
}
.error {
color: var(--danger);
font-size: 13px;
min-height: 18px;
}
.small {
font-size: 13px;
color: var(--muted);
}
.pw-row {
display: flex;
gap: 8px;
align-items: center;
}
.tiny-btn {
font-size: 13px;
padding: 8px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.04);
background: transparent;
color: var(--muted);
cursor: pointer
}
.checkbox label {
display: flex;
align-items: center;
gap: 8px;
color: var(--muted);
}
.actions {
display: flex;
gap: 10px;
margin-top: 6px;
}
.btn {
padding: 10px 14px;
border-radius: 10px;
border: 0;
background: linear-gradient(90deg, var(--accent), #22c1c3);
color: white;
font-weight: 600;
cursor: pointer
}
.btn.secondary {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.06);
color: var(--muted)
}
.strength {
height: 8px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.03);
overflow: hidden;
margin-top: 6px
}
.strength>i {
display: block;
height: 100%;
width: 0%;
}
.form-message {
margin-top: 8px;
color: var(--muted);
font-size: 14px;
min-height: 20px;
}
@media (max-width:600px) {
.pw-row {
flex-direction: row;
}
.card {
padding: 16px;
}
} Javascript Code
// Regex definitions (simple but effective)
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
const PHONE_RE = /^(?:\+91[\s-]?|0)?[6-9]\d{9}$/;
const PWD_RULES = {
length: { test: pw => pw.length >= 8, msg: 'At least 8 characters' },
lower: { test: pw => /[a-z]/.test(pw), msg: 'Lowercase letter' },
upper: { test: pw => /[A-Z]/.test(pw), msg: 'Uppercase letter' },
number: { test: pw => /[0-9]/.test(pw), msg: 'Number' },
special: { test: pw => /[^A-Za-z0-9]/.test(pw), msg: 'Special character' }
};
// Elements
const form = document.getElementById('signupForm');
const nameInput = document.getElementById('name');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const confirmInput = document.getElementById('confirm');
const phoneInput = document.getElementById('phone');
const termsInput = document.getElementById('terms');
const nameError = document.getElementById('nameError');
const emailError = document.getElementById('emailError');
const passwordError = document.getElementById('passwordError');
const confirmError = document.getElementById('confirmError');
const phoneError = document.getElementById('phoneError');
const termsError = document.getElementById('termsError');
const pwBar = document.getElementById('pwBar');
const formMessage = document.getElementById('formMessage');
const togglePwd = document.getElementById('togglePwd');
const resetBtn = document.getElementById('resetBtn');
// Utilities
function setError(el, msg){
el.textContent = msg || '';
}
function setValidState(input, ok){
if(ok) input.style.borderColor = 'rgba(16,185,129,0.6)'; // green
else input.style.borderColor = 'rgba(239,68,68,0.6)'; // red
}
// Validation functions
function validateName(){
const v = nameInput.value.trim();
if(!v){
setError(nameError,'Please enter your full name.');
setValidState(nameInput, false);
return false;
}
setError(nameError,'');
setValidState(nameInput, true);
return true;
}
function validateEmail(){
const v = emailInput.value.trim();
if(!v){
setError(emailError,'Email is required.');
setValidState(emailInput, false);
return false;
}
if(!EMAIL_RE.test(v)){
setError(emailError,'Enter a valid email address.');
setValidState(emailInput, false);
return false;
}
setError(emailError,'');
setValidState(emailInput, true);
return true;
}
function passwordStrength(pw){
// score 0..100 based on rules and length
let score = 0;
if(PWD_RULES.length.test(pw)) score += 30;
if(PWD_RULES.lower.test(pw)) score += 15;
if(PWD_RULES.upper.test(pw)) score += 15;
if(PWD_RULES.number.test(pw)) score += 20;
if(PWD_RULES.special.test(pw)) score += 20;
return Math.min(100, score);
}
function validatePassword(){
const pw = passwordInput.value;
const fails = Object.values(PWD_RULES).filter(r => !r.test(pw)).map(r => r.msg);
if(!pw){
setError(passwordError,'Password is required.');
setValidState(passwordInput, false);
updateStrength(0);
return false;
}
if(fails.length){
setError(passwordError, 'Missing: ' + fails.join(', '));
setValidState(passwordInput, false);
updateStrength(passwordStrength(pw));
return false;
}
setError(passwordError,'');
setValidState(passwordInput, true);
updateStrength(passwordStrength(pw));
return true;
}
function validateConfirm(){
const pw = passwordInput.value;
const c = confirmInput.value;
if(!c){
setError(confirmError,'Please confirm your password.');
setValidState(confirmInput, false);
return false;
}
if(pw !== c){
setError(confirmError,'Passwords do not match.');
setValidState(confirmInput, false);
return false;
}
setError(confirmError,'');
setValidState(confirmInput, true);
return true;
}
function validatePhone(){
const v = phoneInput.value.trim();
if(!v){
setError(phoneError,'');
phoneInput.style.borderColor = '';
return true; // optional
}
if(!PHONE_RE.test(v)){
setError(phoneError,'Enter a valid phone number.');
setValidState(phoneInput, false);
return false;
}
setError(phoneError,'');
setValidState(phoneInput, true);
return true;
}
function validateTerms(){
if(!termsInput.checked){
setError(termsError,'You must accept the terms.');
return false;
}
setError(termsError,'');
return true;
}
// Strength bar UI
function updateStrength(score){
pwBar.style.width = score + '%';
if(score < 35) pwBar.style.background = '#ef4444';
else if(score < 70) pwBar.style.background = '#f59e0b';
else pwBar.style.background = '#10b981';
}
// Event listeners for live validation
nameInput.addEventListener('input', validateName);
emailInput.addEventListener('input', validateEmail);
passwordInput.addEventListener('input', () => {
validatePassword();
validateConfirm(); // re-check confirmation live
});
confirmInput.addEventListener('input', validateConfirm);
phoneInput.addEventListener('input', validatePhone);
termsInput.addEventListener('change', validateTerms);
// Toggle show/hide password
togglePwd.addEventListener('click', () => {
const type = passwordInput.type === 'password' ? 'text' : 'password';
passwordInput.type = type;
confirmInput.type = type;
togglePwd.textContent = type === 'text' ? 'Hide' : 'Show';
togglePwd.setAttribute('aria-pressed', type === 'text');
});
// Reset
resetBtn.addEventListener('click', () => {
form.reset();
// clear errors & styles
[nameError,emailError,passwordError,confirmError,phoneError,termsError].forEach(e => e.textContent = '');
[nameInput,emailInput,passwordInput,confirmInput,phoneInput].forEach(i => i.style.borderColor = '');
updateStrength(0);
formMessage.textContent = '';
});
// Final submit
form.addEventListener('submit', (ev) => {
ev.preventDefault();
formMessage.textContent = '';
const ok = [
validateName(),
validateEmail(),
validatePassword(),
validateConfirm(),
validatePhone(),
validateTerms()
].every(Boolean);
if(!ok){
formMessage.textContent = 'Please fix the errors above before submitting.';
formMessage.style.color = '#f59e0b';
return;
}
// Simulate successful submission (replace with real registration logic)
formMessage.textContent = 'Success! Form validated — ready to submit to the server.';
formMessage.style.color = '#10b981';
// Optionally collect data
const payload = {
name: nameInput.value.trim(),
email: emailInput.value.trim(),
phone: phoneInput.value.trim() || null
// IMPORTANT: in real apps never send plain password unless over HTTPS and following best practices
};
console.log('Validated payload:', payload);
// Optionally clear sensitive fields
passwordInput.value = '';
confirmInput.value = '';
updateStrength(0);
});
Related Projects
Day 1 : Weather App
Fetch and display real-time weather data for any city using an API.
Concepts: API fetch, async/await, JSON handling.
Day 5 : Digital Piano
Play sound notes when user clicks keys or presses keyboard buttons.
Concepts: Audio API, keyboard events.
Day 6 : Tip Calculator
Calculates tip amount and total per person based on bill and tip %.
Concepts: Form inputs, math logic.