Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🖱️ Days 8–14 – Data, DOM & Events  ·  Day 13

Day 13: Forms, FormData & Validation

⏱ 100 min📶 Intermediate🎯 100% self-study
Day 13 / 30

✅ Today you will learn

  • Handle submit with preventDefault
  • Read all fields with FormData and Object.fromEntries
  • Write clear validation rules (mobile, PIN, quantity, date)
  • Show accessible error messages next to fields
  • Constraint Validation API and live calculations

🏢 In the real world

The HTML-only order form from the HTML course stopped obviously wrong data, but customers still ordered for yesterday’s date, typed PIN codes outside the delivery area and got a vague browser bubble in English only. Businesses lose orders on bad forms. Today you build a friendly, bilingual-ready form that explains every mistake and calculates the total live.

Handling submit and reading values

HTML + JavaScript
<style>
form { display: grid; gap: 10px; max-width: 380px; }
label { font-weight: 600; display: block; margin-bottom: 2px; }
input, select { width: 100%; box-sizing: border-box; }
.error { color: #b91c1c; font-size: .85rem; min-height: 1.1em; }
input[aria-invalid="true"], select[aria-invalid="true"] { border-color: #dc2626; background: #fef2f2; }
.ok { background: #dcfce7; padding: 10px; border-radius: 8px; }
</style>

<form id="f">
  <label for="name">Name</label><input id="name" name="name" value="Aarti Gupta">
  <label for="sweet">Sweet</label>
  <select id="sweet" name="sweet"><option value="1">Kaju Katli</option><option value="2" selected>Ghewar</option></select>
  <label><input type="checkbox" name="gift" checked> Gift wrap</label>
  <fieldset><legend>Delivery</legend>
    <label><input type="radio" name="mode" value="home" checked> Home</label>
    <label><input type="radio" name="mode" value="pickup"> Pick-up</label>
  </fieldset>
  <button>Submit</button>
</form>
<pre id="out"></pre>

<script>
document.querySelector('#f').addEventListener('submit', function (e) {
  e.preventDefault();                                   // no page reload
  const data = new FormData(e.currentTarget);           // reads every named field
  const order = Object.fromEntries(data);               // → plain object
  order.gift = data.has('gift');                        // unchecked boxes are missing
  order.sweet = Number(order.sweet);
  document.querySelector('#out').textContent = JSON.stringify(order, null, 2);
});
</script>
▶  Submit: FormData turns the whole form into an object
🧠 The name attribute matters

FormData only reads fields that have a name. Radio buttons share one name and give the selected value; an unchecked checkbox is not included at all – check it with data.has("gift").

Validation rules as small functions

JavaScript
const DELIVERY_PINS = ['302001', '302003', '302004', '302012', '302017'];

const rules = {
  name:  v => v.trim().length >= 2 || 'Please enter your full name.',
  phone: v => /^[6-9]\d{9}$/.test(v.replace(/\D/g, '').slice(-10)) || 'Enter a 10-digit mobile number starting with 6–9.',
  pin:   v => DELIVERY_PINS.includes(v.trim()) || 'Sorry, we do not deliver to this PIN yet.',
  qty:   v => (Number(v) >= 0.25 && Number(v) <= 50) || 'Quantity must be between 0.25 and 50 kg.',
  date:  (v, today) => v >= today || 'Choose today or a future date.',
};

function validate(order, today) {
  const errors = {};
  for (const [field, rule] of Object.entries(rules)) {
    const result = rule(order[field] ?? '', today);
    if (result !== true) errors[field] = result;
  }
  return errors;
}

const today = '2026-10-25';
console.log(validate({ name: 'Aarti', phone: '+91 98290 12345', pin: '302003', qty: '2', date: '2026-10-26' }, today));
console.log(validate({ name: 'A', phone: '12345', pin: '110001', qty: '0', date: '2026-10-20' }, today));
{} { name: 'Please enter your full name.', phone: 'Enter a 10-digit mobile number starting with 6–9.', pin: 'Sorry, we do not deliver to this PIN yet.', qty: 'Quantity must be between 0.25 and 50 kg.', date: 'Choose today or a future date.' }
💡 Why rules return true or a message

Each rule answers “is it OK?” with true, or explains what is wrong. The same object can be reused on the server (Node.js) – and the messages can be swapped for Hindi versions without touching the logic.

A complete validated order form

HTML + JavaScript
<style>
form { display: grid; gap: 10px; max-width: 380px; }
label { font-weight: 600; display: block; margin-bottom: 2px; }
input, select { width: 100%; box-sizing: border-box; }
.error { color: #b91c1c; font-size: .85rem; min-height: 1.1em; }
input[aria-invalid="true"], select[aria-invalid="true"] { border-color: #dc2626; background: #fef2f2; }
.ok { background: #dcfce7; padding: 10px; border-radius: 8px; }
</style>

<form id="order" novalidate>
  <div><label for="name">Name</label><input id="name" name="name" autocomplete="name" aria-describedby="name-err"><div class="error" id="name-err"></div></div>
  <div><label for="phone">Mobile</label><input id="phone" name="phone" type="tel" inputmode="numeric" autocomplete="tel" aria-describedby="phone-err"><div class="error" id="phone-err"></div></div>
  <div><label for="pin">PIN code</label><input id="pin" name="pin" inputmode="numeric" maxlength="6" aria-describedby="pin-err"><div class="error" id="pin-err"></div></div>
  <div><label for="qty">Kaju Katli (kg) – ₹900/kg</label><input id="qty" name="qty" type="number" value="1" step="0.25" aria-describedby="qty-err"><div class="error" id="qty-err"></div></div>
  <div><label for="date">Delivery date</label><input id="date" name="date" type="date" aria-describedby="date-err"><div class="error" id="date-err"></div></div>
  <p id="total"></p>
  <button>Place order</button>
  <div id="result" role="status"></div>
</form>

<script>
const PINS = ['302001', '302003', '302004', '302012', '302017'];
const today = new Date().toISOString().slice(0, 10);
const form = document.querySelector('#order');
form.date.min = today;

const rules = {
  name:  v => v.trim().length >= 2 || 'Please enter your full name.',
  phone: v => /^[6-9]\d{9}$/.test(v.replace(/\D/g, '').slice(-10)) || 'Enter a 10-digit mobile number starting with 6–9.',
  pin:   v => PINS.includes(v.trim()) || 'We deliver to 302001, 302003, 302004, 302012, 302017.',
  qty:   v => (Number(v) >= 0.25 && Number(v) <= 50) || 'Between 0.25 and 50 kg.',
  date:  v => (v !== '' && v >= today) || 'Choose today or a future date.',
};

function showError(field, message) {
  const input = form.elements[field];
  input.setAttribute('aria-invalid', message ? 'true' : 'false');
  document.querySelector('#' + field + '-err').textContent = message || '';
}
function check(field) {
  const result = rules[field](form.elements[field].value);
  showError(field, result === true ? '' : result);
  return result === true;
}
function updateTotal() {
  const kg = Number(form.qty.value) || 0;
  const amount = kg * 900;
  const delivery = amount >= 1500 || amount === 0 ? 0 : 60;
  document.querySelector('#total').textContent = `Total ₹${amount + delivery} (delivery ₹${delivery})`;
}

Object.keys(rules).forEach(f => form.elements[f].addEventListener('blur', () => check(f)));   // validate on leave
form.addEventListener('input', e => {
  if (e.target.getAttribute('aria-invalid') === 'true') check(e.target.name);   // re-check while fixing
  if (e.target.name === 'qty') updateTotal();
});
form.addEventListener('submit', e => {
  e.preventDefault();
  const results = Object.keys(rules).map(check);
  const firstBad = Object.keys(rules).find((f, i) => !results[i]);
  if (firstBad) { form.elements[firstBad].focus(); return; }          // move focus to the first error
  const data = Object.fromEntries(new FormData(form));
  document.querySelector('#result').innerHTML = `<p class="ok">✅ Order for ${data.qty} kg booked for ${data.date}. We will call ${data.phone}.</p>`;
});
updateTotal();
</script>
▶  Try submitting empty, then fix each field – errors update live
UX ruleHow we did it
Do not shout before the user typesValidate on blur and on submit, not on page load
Clear the error as soon as it is fixedRe-check on input only for invalid fields
Say exactly what to do“Enter a 10-digit mobile number starting with 6–9”
Accessible errorsaria-invalid + aria-describedby link the message to the field
Help users get to the problemFocus the first invalid field on submit
Accept human formats+91 98290-12345 → digits are extracted before checking
⚠️ Client-side validation is for convenience only

Anyone can bypass JavaScript (DevTools, curl). The server must validate everything again – our practice API on Day 20 does exactly that and returns field errors with HTTP 422.

The built-in Constraint Validation API

HTML + JavaScript
<style>
form { display: grid; gap: 10px; max-width: 380px; }
label { font-weight: 600; display: block; margin-bottom: 2px; }
input, select { width: 100%; box-sizing: border-box; }
.error { color: #b91c1c; font-size: .85rem; min-height: 1.1em; }
input[aria-invalid="true"], select[aria-invalid="true"] { border-color: #dc2626; background: #fef2f2; }
.ok { background: #dcfce7; padding: 10px; border-radius: 8px; }
</style>

<form id="f2">
  <label for="email">E-mail</label>
  <input id="email" type="email" required>
  <button>Subscribe</button>
</form>

<script>
const email = document.querySelector('#email');
email.addEventListener('input', () => {
  if (email.validity.typeMismatch) email.setCustomValidity('Please enter an e-mail like name@example.com');
  else if (email.validity.valueMissing) email.setCustomValidity('E-mail is required to get offers');
  else email.setCustomValidity('');              // '' = valid again
});
document.querySelector('#f2').addEventListener('submit', e => {
  e.preventDefault();
  if (!e.currentTarget.checkValidity()) { e.currentTarget.reportValidity(); return; }
  e.currentTarget.insertAdjacentHTML('beforeend', '<p class="ok">Subscribed ✅</p>');
});
</script>
▶  validity + setCustomValidity + reportValidity: custom text in the native bubble

🏅 Job skills unlocked today

✔ Handle submit and read data with FormData✔ Write reusable validation rules✔ Display accessible, helpful error messages✔ Use the Constraint Validation API and live totals

Practice tasks

🏋️ Practice – do it yourself

Task 1: Write a password strength checker: at least 8 characters, one digit, one capital letter; show which rules are met as ✔/✖ while typing.
Show solution
▶  Result
Task 2: Add a “Confirm mobile” field and show an error if it does not match the first mobile field.
Task 3: Translate all error messages of the order form into Hindi using an object messages = { en: {...}, hi: {...} }.

Quick quiz

🧠 Quick Quiz – test yourself

1. FormData reads fields that have…
name is the key.
2. An unchecked checkbox in FormData is…
Use data.has().
3. Best moment to first show an error?
Do not scold early.
4. Link an error message to its input for screen readers:
Plus aria-invalid.
5. Is client-side validation enough for security?
It can be bypassed.

Interview corner

💼 Interview Corner

How do you validate forms well in vanilla JS?

Use HTML attributes for basics, centralise rules as functions, validate on blur/submit, show inline accessible messages (aria-invalid, aria-describedby), focus the first error, normalise input (trim, digits) and always re-validate on the server.

What does e.preventDefault() do on submit?

It stops the browser’s default navigation/reload so JavaScript can validate and send the data (e.g. with fetch) while staying on the page.

Summary

  • submit + preventDefault + FormData + Object.fromEntries.
  • Rules as small functions returning true or a message.
  • Validate on blur/submit, show accessible inline errors, focus the first one.
  • Client checks are UX; the server must validate again.
➡️ Next

Day 14 – Mini project: a complete to-do app with add, edit, complete, filter, delete and saving.

Frequently Asked Questions

novalidate – why did we add it?
It turns off the browser’s own bubbles so our custom messages control the experience. Our JS still validates everything.
Should I use a form library?
For complex multi-step forms in React, libraries like React Hook Form help. Learn the vanilla version first – the concepts are identical.
← JavaScript (30 Days)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicDay 14: Mini Project: To-Do App Your progress: 13 / 30 (43%) · Lesson 14 / 30 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free