Progressive Enhancement and HTML Forms: use FormData

Progressive Enhancement and HTML Forms
Progressive Enhancement and HTML Forms (Base Illustration by Shopify)

In my article “Embrace the Platform” over at CSS-Tricks I mentioned this experience by Drew Devault:

My web browser has been perfectly competent at submitting HTML forms for the past 28 years, but for some stupid reason some developer decided to reimplement all of the form semantics in JavaScript, and now I can’t pay my electricity bill without opening up the dev tools.

While the article hints at solving the situation using Progressive Enhancement, it doesn’t cover the practical side of things. Let’s change that with this post.



To apply Progressive Enhancement on HTML forms, there’s this little JavaScript gem named FormData that one can use:

The FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the fetch() or XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data".

To use FormData, create a new instance of it and pass in a form element as its first argument. It will automagically do its thing.

const form = document.querySelector('form');
const data = new FormData(form); // 👈 Captures all the form’s key-value pairs

As FormData captures all the form’s key-value pairs, applying Progressive Enhancement on forms becomes pretty easy:

  1. Build a regular HTML form that submits its data to somewhere
  2. Make it visually interesting with CSS
  3. Using JavaScript, hijack the form’s submit event and, instead, send its contents — captured through FormData — using fetch() to the defined endpoint.

A first iteration of the JavaScript code for step 3 would look like this:

document.querySelector("form").addEventListener("submit", async (event) => {

  const form = event.currentTarget;
  const resource = form.action;
  const options = {
    method: form.method,
    body: new FormData(form) // 👈 Magic!

  const r = await fetch(resource, options);

  if (!r.ok) {
    // @TODO: Show an error message

  // @TODO: handle the response by showing a message, redirecting, etc.

Congratulations, you’ve just progressively enhanced your form!


Dealing with JSON and GET

While this basic approach already works, it doesn’t cover all scenarios, mainly due to the way how FormData encodes the data:

The FormData interface […] uses the same format a form would use if the encoding type were set to "multipart/form-data".

So if you want to send the data as JSON, you will need to convert the formData to it yourself. Thankfully, using Modern JavaScript, that’s only a one-liner nowadays. Furthermore the code also doesn’t properly handle GET requests. To cater for those one doesn’t need to send the formData as the request body, but alter the resource’s query string parameters instead.

Expressed in code, we need these adjustments:

const resource = new URL(form.action || window.location.href);

// …

if (options.method === "get") { = new URLSearchParams(formData);
} else {
  if (form.enctype === "multipart/form-data") {
    options.body = formData;
  } else {
    options.body = JSON.stringify(Object.fromEntries(formData));
    options.headers['Content-Type'] = 'application/json';



Embedded below is a CodePen demo that submits the data to a dummy API and then handles its response:

See the Pen
Progressive Enhancement and <form>: Use FormData
by Bramus (@bramus)
on CodePen.



Next steps

The core of this post evolves around using FormData + fetch() to capture and send the data. As for next steps, the experience can still further be improved by – for example – preventing double form submissions and showing a loading spinner.

Example of a loading indicator while the form is submitting


Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Join the Conversation

1 Comment

  1. Thank you for these tips, but the only way we can get forms to comply with WCAG is to use JavaScript as the error handling in HTML forms is broken. That is the reason people hack forms to add some functionality in there.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.