Chapter 13: Forms & Validation
Forms & Validation! ☕
Forms are how users talk back to your website — sign up, log in, search, contact, order food, post comments, apply for jobs… almost every interactive website needs them.
Validation is the police that checks: “Is the data sensible before we send it to the server?”
We’ll cover:
- Why forms + validation matter
- Basic form structure
- All important input types & attributes
- Built-in HTML5 validation (client-side, no JS needed at first)
- Styling valid/invalid states with CSS
- Simple JavaScript enhancement (for custom messages)
- Best practices in 2026
And yes — a complete, realistic example you can copy-paste right now.
1. What is an HTML Form?
|
0 1 2 3 4 5 6 7 8 |
<form action="/submit" method="POST"> <!-- All inputs, labels, buttons live here --> </form> |
Key attributes:
- action = where data goes (URL or empty for current page)
- method = GET (visible in URL, bookmarkable) or POST (hidden, for sensitive data like passwords)
- novalidate = disable browser validation (useful when you want full JS control)
2. Core Form Controls (Inputs & Friends)
| Control | HTML Tag & Type | Best For | Validation Attributes You’ll Use Often |
|---|---|---|---|
| Text | <input type=”text”> | Name, username, search | required, minlength, maxlength, pattern |
| <input type=”email”> | Email addresses | Auto-checks @ and domain | |
| Password | <input type=”password”> | Passwords | required, minlength |
| Number | <input type=”number”> | Age, quantity, rating | min, max, step |
| Tel | <input type=”tel”> | Phone numbers | pattern for format |
| Date | <input type=”date”> | Birthdate, appointment | min, max |
| Checkbox | <input type=”checkbox”> | Agree to terms, multiple choices | required |
| Radio | <input type=”radio”> | Single choice (gender, payment method) | required on group |
| Select (dropdown) | <select> + <option> | Country, city, category | required |
| Textarea | <textarea> | Message, bio, comments | required, minlength, maxlength |
| File | <input type=”file”> | Upload photo/resume | accept (file types) |
| Submit | <button type=”submit”> or <input type=”submit”> | Send button | — |
Always wrap inputs with <label> for accessibility:
|
0 1 2 3 4 5 6 7 |
<label for="email">Email:</label> <input type="email" id="email" name="email" required> |
3. HTML5 Built-in Validation (No JavaScript Needed!)
HTML5 gives powerful constraint validation — browser shows bubbles automatically.
Most useful attributes:
- required → must be filled
- minlength / maxlength → character count
- min / max → for numbers/date
- pattern=”regex” → custom format (e.g. Indian phone: pattern=”[0-9]{10}”)
- type=”email” / url / tel → built-in format check
- step → for number/date (e.g. step=”0.01″ for price)
CSS pseudo-classes to style:
|
0 1 2 3 4 5 6 7 8 |
input:valid { border: 2px solid green; } input:invalid { border: 2px solid red; } input:focus:invalid { outline: 3px solid orange; } |
4. Complete Realistic Example – Contact / Signup Form
index.html
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Contact Form with Validation – Webliance</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="container"> <h1>Get in Touch</h1> <p>Hyderabad-based learner? Let's chat about web dev! ☕</p> <form id="contactForm" action="#" method="POST" novalidate> <div class="form-group"> <label for="name">Full Name *</label> <input type="text" id="name" name="name" required minlength="3" placeholder="Webliance Kumar"> <span class="error"></span> </div> <div class="form-group"> <label for="email">Email *</label> <input type="email" id="email" name="email" required placeholder="webliance@example.com"> <span class="error"></span> </div> <div class="form-group"> <label for="phone">Phone (10 digits)</label> <input type="tel" id="phone" name="phone" pattern="[0-9]{10}" placeholder="9876543210"> <span class="error"></span> </div> <div class="form-group"> <label for="age">Age</label> <input type="number" id="age" name="age" min="18" max="100" step="1"> </div> <div class="form-group"> <label for="message">Message *</label> <textarea id="message" name="message" rows="5" required minlength="10" placeholder="Tell me about your project or question..."></textarea> <span class="error"></span> </div> <div class="form-group checkbox"> <input type="checkbox" id="terms" name="terms" required> <label for="terms">I agree to the terms & privacy policy</label> <span class="error"></span> </div> <button type="submit">Send Message</button> </form> </div> <script src="script.js"></script> </body> </html> |
style.css (clean + validation styling)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
* { margin:0; padding:0; box-sizing:border-box; } body { font-family: system-ui, sans-serif; background: #f8fafc; color: #1e293b; padding: 40px 20px; } .container { max-width: 600px; margin: 0 auto; background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } h1 { text-align: center; margin-bottom: 1rem; color: #1d4ed8; } .form-group { margin-bottom: 1.5rem; } label { display: block; margin-bottom: 0.5rem; font-weight: 600; } input, textarea, select { width: 100%; padding: 12px; border: 2px solid #cbd5e1; border-radius: 6px; font-size: 1rem; } input:focus, textarea:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.2); } /* Built-in validation styling */ input:invalid:not(:placeholder-shown):not(:focus), textarea:invalid:not(:placeholder-shown):not(:focus) { border-color: #ef4444; } input:valid:not(:placeholder-shown), textarea:valid:not(:placeholder-shown) { border-color: #10b981; } .error { color: #ef4444; font-size: 0.9rem; margin-top: 0.3rem; display: block; min-height: 1.2rem; } button { width: 100%; padding: 14px; background: #1d4ed8; color: white; border: none; border-radius: 8px; font-size: 1.1rem; cursor: pointer; } button:hover { background: #1e40af; } .checkbox { display: flex; align-items: center; gap: 10px; } .checkbox input { width: auto; } |
script.js (simple custom messages – optional enhancement)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
const form = document.getElementById('contactForm'); const inputs = form.querySelectorAll('input[required], textarea[required]'); form.addEventListener('submit', (e) => { let valid = true; inputs.forEach(input => { const errorSpan = input.nextElementSibling?.classList.contains('error') ? input.nextElementSibling : null; if (!input.validity.valid) { valid = false; if (errorSpan) { if (input.validity.valueMissing) { errorSpan.textContent = 'This field is required'; } else if (input.validity.tooShort) { errorSpan.textContent = `Minimum ${input.minLength} characters`; } else if (input.validity.patternMismatch) { errorSpan.textContent = 'Please enter a valid 10-digit phone number'; } else if (input.type === 'email' && input.validity.typeMismatch) { errorSpan.textContent = 'Please enter a valid email'; } else { errorSpan.textContent = 'Invalid input'; } } } else if (errorSpan) { errorSpan.textContent = ''; } }); if (!valid) { e.preventDefault(); // stop submission } else { alert('Form submitted successfully! 🎉 (In real app → send to server)'); } }); // Clear error on input inputs.forEach(input => { input.addEventListener('input', () => { const errorSpan = input.nextElementSibling; if (errorSpan && errorSpan.classList.contains('error')) { errorSpan.textContent = ''; } }); }); |
How to Test
- Save all three files in same folder
- Open index.html with Live Server
- Try submitting empty → browser bubble + our red borders & messages
- Enter invalid email/phone → see custom messages
- Fill correctly → success alert
Quick Best Practices Summary (2026)
- Always use required + proper type first (HTML validation)
- Style :invalid / :valid for instant feedback
- Add custom JS only for better messages or complex rules
- Never trust client validation alone — always validate again on server
- Use <label for=”id”> + id matching for accessibility
- Mobile? Test on phone — date/number pickers are native & nice
- Add autocomplete=”name” / “email” etc. for better UX
How does the form feel when you play with it? Errors clear? Looks good on mobile?
Next steps you might want:
- “add dark mode to this form”
- “make it responsive with better layout”
- “handle file upload + preview”
- “send form data with fetch API”
- “advanced validation with regex or libraries”
You’re now ready to build real interactive pages — huge milestone! Chai khatam? Let’s keep coding 🚀
