Chapter 17 of 28 · JavaScript for Beginners — Complete Free Tutorial
Forms & User Input
Forms are how applications receive data: logins, search boxes, checkouts, settings. This chapter covers reading input values, validating before submission, the FormData API, and keeping the browser's built-in validation on your side — with the accessibility habits that make forms usable by everyone.
Reading values
const email = document.querySelector('#email');
const age = document.querySelector('#age');
const agree = document.querySelector('#terms');
console.log(email.value); // → live string of what the user typed
console.log(age.valueAsNumber);// → 25 (number, or NaN if invalid)
console.log(agree.checked); // → true / false (checkboxes & radios)
console.log(agree.type); // → 'checkbox'
Text-like inputs expose .value (always a string — convert with Number() or parseFloat()); checkboxes and radios use .checked; <select> uses the chosen option's .value.
The input vs change events
// input — fires on every keystroke/paste (live feedback)
searchInput.addEventListener('input', (e) => {
console.log(`typing: ${e.target.value}`);
});
// change — fires when the value is committed (blur, checkbox, radio, select)
agree.addEventListener('change', (e) => {
submitBtn.disabled = !e.target.checked;
});
Reading selects and radio groups
const plan = document.querySelector('#plan');
plan.addEventListener('change', () => console.log(plan.value)); // selected option value
const selected = document.querySelector('input[name="size"]:checked');
console.log(selected?.value ?? 'none selected');
Radio buttons share a name; exactly one in the group can be checked — query input[name="x"]:checked to find it.
Intercepting submission
const form = document.querySelector('#signup');
form.addEventListener('submit', (e) => {
e.preventDefault(); // ← the essential line
const data = Object.fromEntries(new FormData(form));
console.log(data);
// → { email: 'a@b.c', plan: 'pro' }
});
Expected output: the page does not reload; you receive a plain object of all named fields. (Classic multi-part encoding without JS still works too — <form action="/signup" method="post"> — progressive enhancement at its best.)
FormData: the whole-form shortcut
const fd = new FormData(form);
fd.append('source', 'landing'); // add extra entries
fd.getAll('tags'); // multi-value fields (checkbox arrays)
// To JSON (FormData itself is not JSON-serialisable):
const json = Object.fromEntries(fd.entries());
// Or send it directly to the server — the browser sets the multipart boundary:
fetch('/api/signup', { method: 'POST', body: fd });
Validation: three layers
1. HTML attributes (free and fast)
<input name="email" type="email" required minlength="5">
<input name="age" type="number" min="18" max="120">
<input name="code" pattern="[A-Z]{3}" title="3 uppercase letters">
The browser blocks submission and shows localised messages — zero JavaScript.
2. The Constraint Validation API
const email = form.elements.email;
if (!email.validity.valid) {
console.log(email.validationMessage); // browser's message
if (email.validity.typeMismatch) console.log('Not a valid email');
if (email.validity.tooShort) console.log('Too short');
}
form.checkValidity(); // whole form → boolean
email.setCustomValidity('Domain not allowed'); // custom rule (empty string = OK)
With <form novalidate> you can suppress the popup and render your own inline messages — modern form UIs do exactly this.
3. JavaScript validation (your rules)
function validateEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); // pragmatic check
}
function setError(field, message) {
const el = document.querySelector(`#${field.id}-error`);
el.textContent = message; // visible text…
field.setAttribute('aria-invalid', message ? 'true' : 'false');
field.setAttribute('aria-describedby', el.id); // …and linked to the field
}
Validate on submit for hard rules, and lightly on input for instant feedback (do not yell at users while they are still typing their email).
Disabled buttons and enabling logic
const submit = form.querySelector('[type="submit"]');
const sync = () => {
submit.disabled = !form.checkValidity();
};
form.addEventListener('input', sync);
sync();
Character counters
const bio = document.querySelector('#bio');
const out = document.querySelector('#bio-count');
bio.addEventListener('input', () => {
out.textContent = `${bio.value.length} / ${bio.maxLength}`;
});
Accessibility essentials
- Every input needs a real
<label for>(click-to-focus, screen-reader names). - Error text: visible, adjacent, linked via
aria-describedby, andaria-invalid="true". - Do not rely on placeholder as the only label — it vanishes on typing.
- Keyboard order should follow visual order (
tabindexhacks are a red flag). - Touch targets ≥ 44 px (mirrors the responsive rules in Responsive Design).
Common mistakes
- Missing
preventDefault— the page reloads and your handler's state evaporates. - Reading
.valueonce at load — it must be read inside the submit handler, after typing happens. - Client-side validation as security — it is UX only; the server must always re-validate (edit the HTML in dev tools and any client check is gone).
- Forgetting the
nameattribute — fields without names are invisible toFormDataand native submission. - Blocking submit with JS only — no-JS users lose the form entirely; keep HTML validation as the floor.
Practice checklist
- Build a signup form: HTML validation attributes + JS submit handler + inline error messages.
- Add a live character counter with a visual warning above the limit.
- Log a full form to an object two ways: manual reading and
Object.fromEntries(new FormData(f)). - Disable submit until
checkValidity()passes, and confirm keyboard-only flow works.
Forms fail — code fails. Learning to handle failure gracefully is next: Error Handling & Debugging.
Frequently Asked Questions
JavaScript is the programming language of the web. It adds behaviour to pages: responding to clicks and input, validating forms, fetching data from APIs, updating the DOM without reloading, and much more. It also runs on servers (Node.js), in desktop and mobile apps, and in build tooling.
They are unrelated languages that share a similar name for historical marketing reasons. Java is a statically-typed compiled language; JavaScript is a dynamically-typed interpreted language designed for the browser. Knowing one does not imply knowledge of the other.
Helpful but not mandatory — HTML gives you the structure JavaScript manipulates and CSS the styling it toggles. If you know basic tags, classes and selectors, you have enough to start; the DOM chapters assume only that much.
Use const by default and let when the value must change. Avoid var entirely in modern code: it is function-scoped instead of block-scoped, hoists differently, and can create accidental globals. This course uses const and let throughout.
The Document Object Model is the browser’s in-memory tree of objects representing your HTML. JavaScript reads and modifies that tree — selecting elements, changing text, adding nodes, attaching event listeners — and the browser re-renders the page to match.
== compares after converting types (so 5 == "5" is true), while === requires the same value AND the same type (5 === "5" is false). Loose equality has famously confusing rules — use === and !== everywhere unless you have a specific reason not to.
JavaScript is single-threaded, so long-running work (timers, fetches, events) is handled by the browser and its callbacks are queued. The event loop moves queued callbacks onto the call stack only when it is empty, with promise callbacks (microtasks) taking priority over timers. It is why asynchronous code never freezes the page.
A Promise is an object representing a future value — pending, fulfilled or rejected — that can be chained and combined (Promise.all). async/await is syntax sugar over promises that lets asynchronous code read top-to-bottom with ordinary try/catch error handling.
Yes. Frameworks are built on exactly these fundamentals — components are functions, state is your variable discipline, effects are events, rendering is DOM work. Developers who learn vanilla first understand what the framework is doing and debug far faster.
Type out the examples instead of copying, build small end-to-end projects (expense tracker, weather app, quiz), handle every UI state (loading, empty, success, error), and revisit fundamentals while reading real code. Consistency beats volume — a little most days outpaces occasional marathons.