README
2 min read18 headings
10.5 Forms and User Input
π Table of Contents
- Form Basics
- Accessing Form Elements
- Reading Form Values
- Form Validation
- FormData API
- Input Events
- Best Practices
Form Basics
Forms are the primary way to collect user input on the web.
Form Structure
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β <form> β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β <label> <input type="text"> β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β <label> <input type="email"> β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β <label> <textarea> β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β <button type="submit">Submit</button> β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Common Input Types
| Type | Purpose | Example |
|---|---|---|
| text | Single line text | <input type="text"> |
| password | Hidden text input | <input type="password"> |
| Email with validation | <input type="email"> | |
| number | Numeric input | <input type="number" min="0" max="100"> |
| checkbox | Boolean true/false | <input type="checkbox"> |
| radio | One of many options | <input type="radio" name="group"> |
| select | Dropdown selection | <select><option>...</option></select> |
| textarea | Multi-line text | <textarea rows="4"></textarea> |
| date | Date picker | <input type="date"> |
| file | File upload | <input type="file"> |
| range | Slider control | <input type="range" min="0" max="100"> |
| color | Color picker | <input type="color"> |
Accessing Form Elements
By Document Methods
// By form id
const form = document.getElementById('myForm');
// By name attribute (returns HTMLCollection)
const forms = document.forms;
const myForm = document.forms['myForm'];
const firstForm = document.forms[0];
Accessing Form Fields
const form = document.getElementById('registrationForm');
// By elements collection (name or id)
const usernameField = form.elements['username'];
const emailField = form.elements['email'];
// By index
const firstField = form.elements[0];
// By querySelector within form
const passwordField = form.querySelector('input[type="password"]');
Form Hierarchy
document.forms
βββ form[0] (document.forms['formName'])
β βββ elements[0]
β βββ elements['fieldName']
β βββ elements.length
βββ form[1]
βββ ...
Reading Form Values
Different Input Types
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Reading Form Values β
ββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β Input Type β How to Get Value β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β text, email, β element.value β
β password, etc. β β Returns string β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β checkbox β element.checked β
β β β Returns boolean β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β radio β Find checked: querySelector(':checked') β
β β β Then get .value β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β select β select.value (selected option's value) β
β β select.selectedIndex (index number) β
β β select.options[i] (specific option) β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β select multiple β Get all selected options β
β β [...select.selectedOptions] β
ββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ€
β file β input.files β FileList β
β β input.files[0] β First File β
ββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
Code Examples
// Text input
const name = document.getElementById('name').value;
// Checkbox
const isSubscribed = document.getElementById('subscribe').checked;
// Radio buttons
const selectedGender = document.querySelector(
'input[name="gender"]:checked'
)?.value;
// Select dropdown
const country = document.getElementById('country').value;
// Multiple select
const selectedOptions = [
...document.getElementById('skills').selectedOptions,
].map((option) => option.value);
// File input
const files = document.getElementById('avatar').files;
if (files.length > 0) {
const firstFile = files[0];
console.log(firstFile.name, firstFile.size, firstFile.type);
}
Form Validation
HTML5 Built-in Validation
<!-- Required field -->
<input type="text" required />
<!-- Pattern matching -->
<input type="text" pattern="[A-Za-z]{3,}" title="3+ letters" />
<!-- Length constraints -->
<input type="text" minlength="3" maxlength="20" />
<!-- Numeric constraints -->
<input type="number" min="0" max="100" step="5" />
<!-- Email validation -->
<input type="email" required />
Validation Flow
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Form Submission Flow β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β User clicks β
β Submit button β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Browser runs β
β HTML5 validationβ
ββββββββββ¬βββββββββ
β
ββββββββββββββββ΄βββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Validation β β Validation β
β FAILS β β PASSES β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ
β β
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Show error β β 'submit' event β
β (no event) β β is fired β
βββββββββββββββββββ ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β JavaScript β
β handler runs β
ββββββββββ¬βββββββββ
β
βββββββββββββββββββ΄ββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β Custom β β Call β
β validation β β e.preventDefaultβ
β passes β β to stop submit β
ββββββββββ¬βββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Form submitted β
β or AJAX sent β
βββββββββββββββββββ
Constraint Validation API
const input = document.getElementById('email');
// Check validity
input.validity.valid; // Overall validity
input.validity.valueMissing; // Required but empty
input.validity.typeMismatch; // Wrong type (email, url)
input.validity.patternMismatch; // Doesn't match pattern
input.validity.tooShort; // Below minlength
input.validity.tooLong; // Above maxlength
input.validity.rangeUnderflow; // Below min
input.validity.rangeOverflow; // Above max
input.validity.stepMismatch; // Doesn't match step
input.validity.customError; // Custom error set
// Validation methods
input.checkValidity(); // Returns true/false
input.reportValidity(); // Shows error + returns boolean
input.setCustomValidity(msg); // Set custom error message
Validity States Table
| Property | Triggers When |
|---|---|
| valueMissing | Required field is empty |
| typeMismatch | Email/URL doesn't match format |
| patternMismatch | Value doesn't match pattern regex |
| tooShort | Value shorter than minlength |
| tooLong | Value longer than maxlength |
| rangeUnderflow | Number below min |
| rangeOverflow | Number above max |
| stepMismatch | Number doesn't match step increments |
| badInput | Browser can't convert input |
| customError | setCustomValidity() was called with msg |
Custom Validation
const form = document.getElementById('registrationForm');
const password = document.getElementById('password');
const confirmPassword = document.getElementById('confirmPassword');
// Custom validation function
function validatePassword() {
if (password.value !== confirmPassword.value) {
confirmPassword.setCustomValidity("Passwords don't match");
} else {
confirmPassword.setCustomValidity(''); // Clear error
}
}
// Validate on input
password.addEventListener('input', validatePassword);
confirmPassword.addEventListener('input', validatePassword);
// Form submission with validation
form.addEventListener('submit', function (e) {
e.preventDefault();
if (form.checkValidity()) {
// All valid, proceed
console.log('Form is valid!');
// form.submit(); or send via fetch()
} else {
// Show validation errors
form.reportValidity();
}
});
FormData API
Creating FormData
// From a form element
const form = document.getElementById('myForm');
const formData = new FormData(form);
// Create empty and append
const formData = new FormData();
formData.append('username', 'john_doe');
formData.append('email', 'john@example.com');
FormData Methods
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FormData Methods β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββ€
β Method β Description β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββ€
β append(name, value) β Add a new value (allows duplicates) β
β set(name, value) β Set value (replaces existing) β
β get(name) β Get first value for name β
β getAll(name) β Get all values for name as array β
β has(name) β Check if name exists β
β delete(name) β Remove all values for name β
β entries() β Iterator of [name, value] pairs β
β keys() β Iterator of all keys β
β values() β Iterator of all values β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββ
Using FormData
const form = document.getElementById('contactForm');
form.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData(form);
// Read values
console.log(formData.get('name'));
console.log(formData.get('email'));
// Iterate all entries
for (const [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
// Convert to object
const data = Object.fromEntries(formData);
console.log(data);
// Send via fetch
try {
const response = await fetch('/api/contact', {
method: 'POST',
body: formData, // Content-Type auto-set to multipart/form-data
});
if (response.ok) {
console.log('Form submitted successfully!');
}
} catch (error) {
console.error('Submission failed:', error);
}
});
FormData with Files
const form = document.getElementById('uploadForm');
form.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData(form);
// Files are automatically included
const file = formData.get('avatar');
console.log('File name:', file.name);
console.log('File size:', file.size);
console.log('File type:', file.type);
// Add additional data
formData.append('uploadedAt', new Date().toISOString());
// Send with fetch (multipart/form-data)
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
});
FormData vs JSON
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FormData vs JSON Comparison β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ€
β FormData β JSON β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ€
β Supports file uploads β Files need Base64 encoding β
β multipart/form-data β application/json β
β Server sees like form β Server needs JSON parser β
β Multiple same-name keys β Last value wins (in object) β
β Automatic encoding β Manual JSON.stringify β
βββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββ
// Sending as JSON instead
const form = document.getElementById('myForm');
form.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData);
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
});
Input Events
Event Types
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Form Input Events β
ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Event β When It Fires β
ββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββ€
β input β Value changes (typing, paste, etc.) β
β change β Value changes AND element loses focus β
β focus β Element receives focus β
β blur β Element loses focus β
β submit β Form is submitted β
β reset β Form reset button clicked β
β invalid β Element fails validation on submit β
β select β Text is selected in input/textarea β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββ
Event Timeline
User types "Hello" and tabs away:
H e l l o [Tab]
β β β β β β
βΌ βΌ βΌ βΌ βΌ βΌ
ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ
βinput β βinput β βinput β βinput β βinput β βblur β βchangeβ
β"H" β β"He" β β"Hel" β β"Hell"β β"Helloβ β β β β
ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ ββββββββ
Note: 'change' fires AFTER 'blur' when value has changed
Input vs Change
const input = document.getElementById('search');
// Fires on every keystroke/change
input.addEventListener('input', function (e) {
console.log('Input event:', e.target.value);
// Good for: live search, real-time validation
});
// Fires when focus leaves AND value changed
input.addEventListener('change', function (e) {
console.log('Change event:', e.target.value);
// Good for: final validation, saving drafts
});
Focus Events
const emailInput = document.getElementById('email');
emailInput.addEventListener('focus', function () {
this.parentElement.classList.add('focused');
showHint('Enter your email address');
});
emailInput.addEventListener('blur', function () {
this.parentElement.classList.remove('focused');
validateEmail(this.value);
});
Form Submit and Reset
const form = document.getElementById('myForm');
// Handle submission
form.addEventListener('submit', function (e) {
e.preventDefault(); // Stop default submission
// Process form...
console.log('Form submitted!');
});
// Handle reset
form.addEventListener('reset', function (e) {
// Optionally prevent reset
if (!confirm('Clear all fields?')) {
e.preventDefault();
}
});
// Programmatically submit/reset
form.submit(); // Submit form
form.reset(); // Reset form
Best Practices
1. Always Use Labels
<!-- Implicit label -->
<label>
Name:
<input type="text" name="name" />
</label>
<!-- Explicit label (preferred for styling) -->
<label for="email">Email:</label>
<input type="email" id="email" name="email" />
2. Provide Good UX Feedback
const input = document.getElementById('username');
const feedback = document.getElementById('usernameFeedback');
input.addEventListener('input', function () {
if (this.validity.valid) {
feedback.textContent = 'β Looks good!';
feedback.className = 'feedback success';
} else if (this.validity.valueMissing) {
feedback.textContent = 'Username is required';
feedback.className = 'feedback error';
} else if (this.validity.tooShort) {
feedback.textContent = `At least ${this.minLength} characters needed`;
feedback.className = 'feedback error';
}
});
3. Debounce Input Events
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
const searchInput = document.getElementById('search');
searchInput.addEventListener(
'input',
debounce(function (e) {
// Only runs 300ms after user stops typing
performSearch(e.target.value);
}, 300)
);
4. Accessibility Considerations
<!-- Use fieldset and legend for groups -->
<fieldset>
<legend>Payment Method</legend>
<label>
<input type="radio" name="payment" value="credit" />
Credit Card
</label>
<label>
<input type="radio" name="payment" value="paypal" />
PayPal
</label>
</fieldset>
<!-- Use aria attributes for custom validation -->
<input type="email" id="email" aria-describedby="emailError" />
<span id="emailError" role="alert" aria-live="polite"></span>
5. Prevent Double Submission
const form = document.getElementById('checkoutForm');
const submitBtn = form.querySelector('button[type="submit"]');
form.addEventListener('submit', async function (e) {
e.preventDefault();
// Disable button
submitBtn.disabled = true;
submitBtn.textContent = 'Processing...';
try {
await submitOrder(new FormData(form));
showSuccess('Order placed!');
} catch (error) {
showError(error.message);
// Re-enable on error
submitBtn.disabled = false;
submitBtn.textContent = 'Place Order';
}
});
Quick Reference
Form Handling Checklist
β‘ preventDefault() on submit
β‘ Validate before processing
β‘ Handle all input types correctly
β‘ Provide visual feedback
β‘ Show loading state
β‘ Handle errors gracefully
β‘ Prevent double submission
β‘ Consider accessibility
Value Extraction Cheat Sheet
// Quick reference
const text = input.value; // text, email, password, etc.
const checked = checkbox.checked; // checkbox
const radio = form.querySelector(':checked').value; // radio
const select = select.value; // select single
const multi = [...select.selectedOptions].map((o) => o.value); // multi
const files = fileInput.files; // file
const formData = new FormData(form); // all form data
const obj = Object.fromEntries(new FormData(form)); // as object
Summary
| Concept | Key Points |
|---|---|
| Accessing Forms | document.forms, form.elements, querySelector |
| Reading Values | .value for most, .checked for checkboxes |
| Validation | HTML5 attributes + Constraint Validation API |
| FormData | new FormData(form), works with fetch |
| Input Event | Real-time, fires on every change |
| Change Event | Fires on blur when value changed |
| Best Practices | Labels, feedback, debounce, accessibility |