7. Handling Forms
Follow along with code examples here!
Clone down the repo, cd into it and run npm i to install dependencies.
Table of Contents:
Key Concepts
Default form behavior - collect input data, refresh the current page, and send the data to the URL specified in the
actionattribute using the HTTPGETmethod (orPOSTif specified)FormData API — the modern approach to extracting all form values as an object
Form validation — checking user input before processing the form data
Form reset — clearing all form inputs after successful submission
Key Syntax
event.preventDefault()— prevents default form submission behaviorform.elements.fieldName.value— access input values by name using form.elementsform.elements.checkboxName.checked— access checkbox checked state (returnstrue/false)new FormData(form)— creates a FormData object from a form elementObject.fromEntries(formData)— converts FormData to a plain JavaScript objectform.reset()— clears all form inputs to their default values
Forms Review: What We Learned in Module 3
In Module 3, we learned how to build forms using HTML and CSS:
How to structure forms with
<form>,<label>,<input>, and<button>elementsDifferent input types:
text,number,email,date,checkbox,radio,<textarea>,<select>Connecting labels to inputs using
forandidattributes for accessibilityThe
nameattribute on inputs (which we'll use extensively in this lesson!)HTML validation attributes:
required,min,max,minlength,maxlength,pattern
We also learned how to capture the form data and send it to Formspree using the action and method attributes:
Recall that after submitting the form you would be redirected to the Formspree.
Why Handle Forms with JavaScript?
Using services like Formspree is great for simple contact forms, but it has limitations:
❌ You lose control — the data is sent away and you can't do anything with it in your app
❌ The page reloads/redirects — this breaks the user experience in modern single-page applications
❌ No custom validation — you can only use basic HTML validation
❌ No dynamic behavior — you can't update the page based on the submitted data
Modern web applications handle forms with JavaScript instead, which allows us to:
✅ Keep users on the same page — no reload or redirect
✅ Do whatever we want with the data — display it, store it, send it to an API
✅ Provide instant feedback — show success messages, validation errors, loading states
✅ Create dynamic experiences — add items to a todo list, submit reviews, create posts
Let's start with a simple form with a single field and a button. Take a look at 0-contact-form/index.html:
Q: Take a look at the textarea element. What is the difference between id and name?
The
idattribute labels the field so that it can be connected to the<label>elementThe
nameattribute gives the form value a name when it is submitted. Remember this!
Remember, we are using the Vite development server. To get the app running on the development server:
cdinto the directorynpm ito install Vite dependencies (we did this earlier at the repo root)npm run devto start the serverctrl+cto stop the server
Handling Forms With JavaScript
Instead of sending the data to another source like Formspree, let's capture the form submission and handle it ourselves.
We'll display a brief status message like "Message Received!" in the empty h3 element and the message in the empty span element

To do this, we'll need to:
Prevent the default page reload/redirect behavior
Extract the form data from the inputs using the
nameattributeUse the data (display it, send it to an API, etc.)
Reset the form
Let's look closer at the key parts of the JavaScript:
contactForm.addEventListener('submit', (event) => {})
The
"submit"event is fired when the user presses the submit button.The event handler should use the
eventparameter for preventing the default behavior.
event.preventDefault()
event.preventDefault()stops the browser from doing its default action (reload/redirect)It must be called at the start of the handler. Otherwise the page will reload and your JavaScript won't run!
Try removing it to see for yourself!
contactForm.elements.message.value
contactForm.elementsis an object containing all inputs in the form.Inside of it, you can access inputs by their
nameattribute (e.g.,form.elements.message).Then, use
.valueto get the current value of the input (e.g.form.elements.message.value)
Status Message
When handling form submissions, it is a good practice to let your user know if the form submission worked!
In this example we always display a success message but you can also show error messages if things like API calls fail
Challenge: Adding A New Input
Let's add name and email inputs to this contact form and display them alongside the message, like this:

HTML
Add two form inputs to the html, one for the name and one for the email
make sure to give them
nameattributes!
add an output element in the
divto display the message (use a<p>with a<span>inside).The format should be
"From: Ada Lovelace (ada@mail.com)".
JavaScript
Grab the output element.
In the event handler, extract the values for the
nameand theemail.Display the formatted message in the output element.
A solution can be found in the
0-contact-forms-solution/folder.
Handling Checkbox Inputs
Let's add a checkbox to our contact form. Suppose we want to give users the option to send the message anonymously.
HTML:
Checkboxes work differently from text inputs. Every form input has a .value property, but for checkboxes:
The
.valueis always the string"on"whether or not the box is actually checked.The
.checkedproperty returnstrueif the box is checked orfalseif not.
So, here is the full JavaScript that uses the checkbox to display the user as "Anonymous" if they so choose:
Tip: You can reduce repetition of formElement.elements by destructuring:
FormData API
The FormData API is a more modern approach to extracting data from a form. It automatically extracts ALL form values into a single FormData object. Since it is not an ordinary object, we need to convert it first using Object.fromEntries():
Let's break this down:
new FormData(form)
Creates a
FormDataobject containing all the form's input valuesAutomatically finds all inputs with a
nameattributeThe
FormDataobject is iterable but not directly usable as a normal object. Instead, it has "entries".
Object.fromEntries(formData)
Converts the FormData object into a plain JavaScript object
Each input's
namebecomes a propertyEach input's
valuebecomes the property value
Often, the FormData and Object.fromEntries calls are combined into one.
Pros:
✅ Concise — just two lines to get all form data
✅ Automatic — grabs all inputs without listing them
✅ Less code to maintain when adding/removing fields
Cons:
❌ Less explicit — harder to see which fields exist
❌ Checkbox gotcha (see below)
FormData Checkboxes
Remember how we said checkbox .value is always "on"? That's exactly what FormData gives you:
And if the checkbox is unchecked, it won't be included at all:
For basic conditionals and ternary operations, this works fine because of JavaScript's truthiness:
However, the most common use case for the FormData is when packaging the entire object to be sent to an API (which we'll see shortly).
Most APIs will prefer receiving a booleans rather than a value that could either be "on"/undefined. So, we often will reassign a checkbox value after extracting it with FormData:
If you have multiple checkboxes, we need to convert each one:
Sending FormData to APIs
Now let's combine what we learned about forms with what we learned about fetch!
Remember, in Module 3 we used Formspree to handle our form submissions. We set the action attribute to the Formspree URL and the form data was sent automatically when submitted. But this caused the page to redirect.
Now, we can use JavaScript to send the data ourselves using fetch with a POST request — keeping the user on the same page:
Let's break down the key parts:
The fetch configuration object:
method: 'POST'— tells the server we're creating/sending new databody: JSON.stringify(formValues)— converts our JavaScript object to a JSON stringheaders: { 'Content-Type': 'application/json', 'accept': 'application/json' }— tells the server we're sending JSON data and are accepting JSON in response
Why use JSON.stringify()?
The body of a fetch request must be a string, not a JavaScript object. JSON.stringify() converts our object into a JSON-formatted string that can be sent over the network:
Handling the response:
Even though we're sending data (not requesting it), the API still sends back a response. We check response.ok to see if it succeeded, then update the UI accordingly.
Challenge: Build Your Own Form with Formspree!
Test your skills by building your own form with Formspree from scratch! We've given you some code to start with in 1-form-challenge/ but it will be up to you to:
Create a new Formspree form
Go to formspree.io and create a free account
Create a new form and copy the endpoint URL (looks like
https://formspree.io/f/xyzabc123)
Create a form with inputs. It is up to you what data you want to collect but your form should have:
At least one text input field
At least one checkbox field
A submit button
A
nameattribute for every inputA
labelfor every input
Use the
FormDataapproach to extract the form values. Remember to update the checkbox inputs!Submit the form data to the Formspree URL using a POST request and
fetch()Render a success or an error message
Don't worry about styling. Just aim for functionality!
Use the example in 0-contact-form-solution-with-post as a guide for what this looks like when completed!
Additional Reading
Other Form Events
Besides the submit event, there are several other useful form events:
input event — fires every time an input's value changes (as you type)
change event — fires when an input's value changes AND the input loses focus
focus and blur events — fires when an input gains or loses focus
Non-submit buttons — buttons with type="button" don't submit the form
This is useful for buttons that manipulate form data without submitting!
Form Validation with JavaScript
HTML validation attributes (required, min, max, etc.) are great, but sometimes we need custom validation logic. JavaScript gives us complete control!
Let's build a registration form with custom validation:
Check out this example in 2-registration-form:
HTML: In the HTML, pay attention to the <span class="error" id="input-name-error"></span> elements that have been added for each input.
CSS: Here, we've created a class to make errors stand out.
JavaScript:
In the JavaScript, pay attention to how the helper functions work within the form submission event handler.
This example demonstrates:
Custom validation logic (username length, password requirements, matching passwords)
Displaying error messages next to the relevant fields
Preventing submission if validation fails
Clearing errors when validation passes
Challenge: Registration Form with Validation
Enhance the registration form above by adding these validation rules:
Username must:
Be at least 3 characters
Contain only letters, numbers, and underscores
HINT: Use regex
/^[a-zA-Z0-9_]+$/
Password must:
Be at least 8 characters
Contain at least one uppercase letter
Contain at least one lowercase letter
Contain at least one number
Add visual feedback:
Input borders turn red when there's an error
Input borders turn green when valid
Add CSS classes
.errorand.validto inputs
Last updated