Forms and User Input
What are forms and user input?
Forms and user input are how users send information to your page. JavaScript can read those values, validate them, respond to changes, and control what happens when a form is submitted with events.
Smallest working example
<label for="username">Username</label>
<input type="text" id="username" />
const input = document.querySelector("#username");
if (input) {
input.addEventListener("input", () => {
console.log("Current value:", input.value);
});
}
Why this matters
Most interactive web pages depend on user input:
- Sign-in and sign-up forms
- Search boxes and filters
- Settings panels and preferences
- Checkout, feedback, and contact forms
Reading input values
Different form controls expose their values in slightly different ways, but all of them start with selecting the right element.
Text inputs
<input type="text" id="username" />
const input = document.querySelector("#username");
if (input) {
const username = input.value;
console.log(username);
input.value = "new value";
input.addEventListener("input", () => {
console.log("Current value:", input.value);
});
}
Textareas
<textarea id="message"></textarea>
const textarea = document.querySelector("#message");
if (textarea) {
const message = textarea.value;
console.log(message);
}
Textareas work like text inputs. Use .value to read and update their content.
Select dropdowns
<select id="country">
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
</select>
const select = document.querySelector("#country");
if (select) {
const country = select.value;
console.log(country);
select.value = "uk";
const selectedOption = select.options[select.selectedIndex];
console.log(selectedOption.text);
}
Checkboxes
<input type="checkbox" id="agree" />
const checkbox = document.querySelector("#agree");
if (checkbox) {
console.log("Checked:", checkbox.checked);
checkbox.checked = true;
checkbox.addEventListener("change", () => {
console.log("Checked:", checkbox.checked);
});
}
Radio buttons
<input type="radio" name="size" value="small" id="size-small" />
<input type="radio" name="size" value="medium" id="size-medium" />
<input type="radio" name="size" value="large" id="size-large" />
const selected = document.querySelector('input[name="size"]:checked');
if (selected) {
console.log("Selected size:", selected.value);
}
const medium = document.querySelector("#size-medium");
if (medium) {
medium.checked = true;
}
document.querySelectorAll('input[name="size"]').forEach(radio => {
radio.addEventListener("change", () => {
console.log("Selected:", radio.value);
});
});
Rule of thumb: use .value for text-like fields, .checked for checkboxes, and :checked when reading the selected radio button.
Handling form submission
By default, forms submit to the server and reload the page. In many JavaScript-driven interfaces, you want to handle submission yourself.
This is one of the most common examples of combining event handling, state, and network requests.
Prevent form submission
<form id="myForm">
<input type="text" name="username" />
<button type="submit">Submit</button>
</form>
const form = document.querySelector("#myForm");
if (form) {
form.addEventListener("submit", e => {
e.preventDefault();
const formData = new FormData(form);
const username = formData.get("username");
console.log("Username:", username);
});
}
Why preventDefault() matters
Without preventDefault(), the browser submits the form right away and usually reloads the page. Preventing the default gives your code a chance to:
- Validate the input first
- Show loading or error states
- Send the data with
fetch() - Update the page without a full reload
Validating form input
Validation helps make sure the user entered acceptable data before you continue.
If your form has multiple moving parts, it often helps to keep validation state separate from the DOM and treat the DOM as a rendered result of that state. See Application State in the Browser.
HTML5 validation
Browsers already support some useful validation rules:
<input type="email" required />
<input type="number" min="0" max="100" />
<input type="text" pattern="[A-Za-z]+" />
const emailInput = document.querySelector('input[type="email"]');
if (emailInput) {
if (emailInput.validity.valid) {
console.log("Valid email");
} else {
console.log("Invalid email:", emailInput.validationMessage);
}
if (emailInput.validity.valueMissing) {
console.log("Email is required");
}
if (emailInput.validity.typeMismatch) {
console.log("Not a valid email format");
}
}
JavaScript validation
Use JavaScript validation when you need rules that HTML alone does not cover:
const form = document.querySelector("#myForm");
if (form) {
form.addEventListener("submit", e => {
e.preventDefault();
clearFormErrors(form);
if (validateForm(form)) {
console.log("Form is valid");
}
});
}
function validateForm(form) {
const usernameInput = form.querySelector("#username");
const emailInput = form.querySelector("#email");
if (!usernameInput || !emailInput) {
return false;
}
const username = usernameInput.value.trim();
const email = emailInput.value.trim();
if (!username) {
showFormError(form, "Username is required");
return false;
}
if (username.length < 3) {
showFormError(form, "Username must be at least 3 characters");
return false;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
showFormError(form, "Invalid email format");
return false;
}
return true;
}
Showing validation errors
function showFormError(form, message) {
clearFormErrors(form);
const errorDiv = document.createElement("div");
errorDiv.className = "error-message";
errorDiv.textContent = message;
errorDiv.setAttribute("role", "alert");
form.prepend(errorDiv);
}
function clearFormErrors(form) {
form.querySelectorAll(".error-message").forEach(error => {
error.remove();
});
}
Real-time validation
const emailInput = document.querySelector("#email");
if (emailInput) {
emailInput.addEventListener("input", () => {
clearFieldError(emailInput);
if (emailInput.value && !isValidEmail(emailInput.value)) {
showFieldError(emailInput, "Invalid email format");
}
});
}
function showFieldError(input, message) {
input.setAttribute("aria-invalid", "true");
let error = input.parentElement?.querySelector(".field-error");
if (!error && input.parentElement) {
error = document.createElement("span");
error.className = "field-error";
input.parentElement.appendChild(error);
}
if (error) {
error.textContent = message;
}
}
function clearFieldError(input) {
input.removeAttribute("aria-invalid");
const error = input.parentElement?.querySelector(".field-error");
if (error) {
error.remove();
}
}
function isValidEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
Real-time validation is helpful, but it can feel noisy if every keystroke shows an error immediately. In many cases, a good pattern is to validate after the user has typed something meaningful or after the field loses focus.
Working with FormData
The FormData API is a convenient way to collect form values.
const form = document.querySelector("#myForm");
if (form) {
form.addEventListener("submit", e => {
e.preventDefault();
const formData = new FormData(form);
const username = formData.get("username");
const email = formData.get("email");
const data = Object.fromEntries(formData);
console.log(username, email, data);
for (const [key, value] of formData.entries()) {
console.log(key, value);
}
});
}
Sending FormData with fetch()
const form = document.querySelector("#myForm");
if (form) {
form.addEventListener("submit", async e => {
e.preventDefault();
const formData = new FormData(form);
await fetch("/api/submit", {
method: "POST",
body: formData
});
});
}
Learn more about making HTTP requests with fetch().
FormData with file uploads
<input type="file" name="avatar" />
const form = document.querySelector("#myForm");
if (form) {
const formData = new FormData(form);
const file = formData.get("avatar");
if (file instanceof File) {
console.log(file.name);
console.log(file.size);
console.log(file.type);
}
}
Accessibility considerations
Accessible forms are easier for everyone to use, including people using screen readers or keyboard navigation.
Use labels
Always associate labels with inputs:
<label for="username">Username</label>
<input type="text" id="username" name="username" />
<label>
Username
<input type="text" name="username" />
</label>
Associate error messages
<label for="email">Email</label>
<input type="email" id="email" name="email" aria-describedby="email-error" />
<span id="email-error" class="error" role="alert"></span>
function showAccessibleError(inputId, message) {
const input = document.querySelector(`#${inputId}`);
const error = document.querySelector(`#${inputId}-error`);
if (input && error) {
input.setAttribute("aria-invalid", "true");
error.textContent = message;
}
}
Mark required fields clearly
<label for="username">
Username <span aria-label="required">*</span>
</label>
<input type="text" id="username" required aria-required="true" />
Manage focus when needed
function showFormErrorAndFocus(form, message) {
const errorDiv = document.createElement("div");
errorDiv.id = "form-error";
errorDiv.textContent = message;
errorDiv.setAttribute("tabindex", "-1");
form.prepend(errorDiv);
errorDiv.focus();
}
Common patterns
Reset a form
const form = document.querySelector("#myForm");
if (form) {
form.reset();
}
Disable the submit button while submitting
const form = document.querySelector("#myForm");
if (form) {
const submitButton = form.querySelector('button[type="submit"]');
if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = "Submitting...";
submitButton.disabled = false;
submitButton.textContent = "Submit";
}
}
Multi-step forms
let currentStep = 1;
function showStep(step) {
document.querySelectorAll(".form-step").forEach((section, index) => {
section.style.display = index + 1 === step ? "block" : "none";
});
}
function nextStep() {
if (validateCurrentStep()) {
currentStep += 1;
showStep(currentStep);
}
}
function previousStep() {
currentStep -= 1;
showStep(currentStep);
}
For more help organizing larger form code, see Structuring Frontend JavaScript.
Summary
- Use
.value,.checked, andFormDatato read user input depending on the kind of field. submithandlers usually callpreventDefault()so your JavaScript can validate and control what happens next.- Start with built-in HTML validation when it fits, then add JavaScript rules for custom cases.
- Good forms are not just functional; they also need labels, clear errors, and accessible focus behavior.