Docs

17.5-Forms-and-User-Input

10.5 Forms and User Input

πŸ“‹ Table of Contents

  1. β€’Form Basics
  2. β€’Accessing Form Elements
  3. β€’Reading Form Values
  4. β€’Form Validation
  5. β€’FormData API
  6. β€’Input Events
  7. β€’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

TypePurposeExample
textSingle line text<input type="text">
passwordHidden text input<input type="password">
emailEmail with validation<input type="email">
numberNumeric input<input type="number" min="0" max="100">
checkboxBoolean true/false<input type="checkbox">
radioOne of many options<input type="radio" name="group">
selectDropdown selection<select><option>...</option></select>
textareaMulti-line text<textarea rows="4"></textarea>
dateDate picker<input type="date">
fileFile upload<input type="file">
rangeSlider control<input type="range" min="0" max="100">
colorColor 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

PropertyTriggers When
valueMissingRequired field is empty
typeMismatchEmail/URL doesn't match format
patternMismatchValue doesn't match pattern regex
tooShortValue shorter than minlength
tooLongValue longer than maxlength
rangeUnderflowNumber below min
rangeOverflowNumber above max
stepMismatchNumber doesn't match step increments
badInputBrowser can't convert input
customErrorsetCustomValidity() 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

ConceptKey Points
Accessing Formsdocument.forms, form.elements, querySelector
Reading Values.value for most, .checked for checkboxes
ValidationHTML5 attributes + Constraint Validation API
FormDatanew FormData(form), works with fetch
Input EventReal-time, fires on every change
Change EventFires on blur when value changed
Best PracticesLabels, feedback, debounce, accessibility
.5 Forms And User Input - JavaScript Tutorial | DeepML