When you’re building forms for the web, getting the semantics, accessibility, and style right is very important. If you can do all that well, you’re doing well. However, there are still things we can do to make life better for people who fill out forms.

In this article, we’ll look at some do’s and don ‘ts of the HTML forms user experience (UX). If you want to review the first few steps mentioned above, check out other articles in this series.

content

Ask for the least amount of information

As an Internet user, I can say from experience that entering more data than necessary into a form is annoying. So if you really only need one email, consider not asking for a first name, last name, and phone number. By creating forms with fewer inputs, you will improve the user experience. Some studies have even shown that smaller forms have higher conversion rates. This is a victory for you. Also, reducing the amount of data you collect has the potential to reduce your privacy concerns, although much depends on the data.

Keep it simple

It’s tempting to use your creativity in form design. However, it’s easy to go overboard and make things confusing. By sticking to simple designs that use standard input types, you’re creating a more cohesive experience, not just on your site, but across the Internet as a whole. This means users are less likely to be fooled by some fancy new input method. Stick to the classics. Keep in mind that selection inputs like checkboxes (which allow multiple selection items) typically use box input, while radios (which allow only one selection) use circles.

Semantics is good for A11y and user experience

I covered semantics in more detail in previous articles, but simply put, choosing the right type of input can improve the experience on many levels: semantics, accessibility, and user experience. People are used to input across the web, so we can take advantage of that and use the same input for the same things. Not to mention there are a lot of things we can get for free, like keyboard navigation support and validation, by using the right input.

Put the country selector before the city/state

This is a simple rule for anyone who adds locality to a form. If you are asking for the user’s country, place it before the city and state fields. Normally, cities and states will fill in according to the country. So, if your country is set to default to the United States and the user lives in Oaxaca, Mexico, they will need to skip the city and state fields, select Mexico, and then fill in their city and state when the list is updated. By putting the country first, you can keep the form flowing, which is especially good for keyboard navigation.

Paginate long tables

This relates to my first point, which is that ideally, you wouldn’t have much data. However, in some cases, it can’t be helped. In these cases, it might make sense to paginate the table so that the information is not overwhelmed. If you choose paging, my best advice is to show the user some kind of user interface about their progress in the form, with the option to remove the paging and display the entire form.

The general function

Prevents browser refresh/navigation

Have you ever been filling out a long form, accidentally refreshing the page and losing all your work? That’s the worst. Fortunately, the browser provides us with beforeunload events (https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event), We can use it to notify users that they are about to lose any unsaved work.

We can set a variable to track if there are any unsaved changes in the form, and we can attach a handler to the beforeUnload event that will prevent the browser from navigating if there are any changes.

// You'll need some variable for tracking the status. We'll call it hasChanges here. window.addEventListener("beforeunload", (event) { if (! hasChanges) return; event.preventDefault(); event.returnValue = ""; }) form.addEventListener('change', () => { hasChanges = true; }); form.addEventListener('submit', () => { hasChanges = false; })Copy the code

The point of this snippet is that we are tracking a variable named hasChanges. If hasChanges is false when the beforeUnload event occurs, we can let the browser navigate smoothly. If hasChanges is true, the browser prompts the user to let them know they have unsaved changes and asks if they want to continue to leave or stay on the page. Finally, we update hasChanges by adding the appropriate event handler to the form.

For the hasChanges variable, your implementation might be slightly different. For example, if you are using a JavaScript framework with some state management, this solution is not suitable if you are creating a single-page application because beforeUnload events do not fire when the single-page application is navigated.

Stores unsaved changes

Along the lines of the previous point, sometimes we accidentally lose all of our work on a long table. Fortunately, we can avoid this pain by taking advantage of the browser’s capabilities, For example, [sessionStorage] (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). For example, we want to store all the data in a form at any time a change event occurs. We can use [FormData] (https://developer.mozilla.org/en-US/docs/Web/API/FormData) to capture the form and all of its current value, The data is then stored as a JSON string in sessionStorage.

JSON

const form = document.querySelector('form')

form.addEventListener('change', event => {
  const formData = new FormData(form);
  sessionStorage.setItem('your-identifier', JSON.stringify(formData));
});
Copy the code

After the data is saved, the user can refresh the data at will and the data will not be lost. The next step is to check localStorage when the page loads to see if we have any previously saved data that can be prepopulated into the form. If so, we could parse the string into an object and loop over each key/value pair, adding the saved data to the respective input. This is slightly different for different input types.

JSON

const previouslySavedData = sessionStorage.getItem('form-data'); if (previouslySavedData) { const inputValues = JSON.parse(savedData); for(const [name, value] of Object.entries(inputValues)) { const input = form.querySelector(`input[name=${name}]`); switch(input.type) { case 'checkbox': input.checked = !! value; break; // other input type logic default: input.value = value; }}}Copy the code

The last thing to do is make sure that after the form is submitted, we clean up any previously saved data. This is part of the reason we use sessionStorage instead of localStorage. We want the data we keep to be unstable in some way.

JSON

form.addEventListener('submit', () => {
  sessionStorage.removeItem('form-data');
});
Copy the code

The last thing to say about this feature is that it’s not suitable for all data. Any private or sensitive data should be excluded from any localStorage persistence. And some input types are unusable at all. For example, there is no way to persist a file input. However, with these caveats understood, it can be a great feature to add to almost any form, especially long forms.

Don’t block copy/paste

One of the most annoying things I’ve experienced recently was on the IRS website. They asked me for my bank account number and my bank routing number. These aren’t short numbers — we’re talking about 15 characters or so. On most sites, this is fine, I copied the numbers from my bank’s website and pasted them into the input box. However, on the IRS website, they chose to disable pasting input, which meant I had to manually fill in the details of each number…… Twice a day. I don’t know why they do this, but it’s very frustrating for users and actually increases the chances of something going wrong. Please don’t do this.

Input function

The input mode

If you haven’t heard of [an inputmode] an inputmode (https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode) Is an HTML input property that lets you tell the browser the input format. This may not be obvious and you wouldn’t notice it if you were on a desktop, but for mobile users it makes a big difference. By selecting different input modes, the browser will present the user with different virtual keyboards to enter their data.

You can greatly improve the mobile user experience of filling out forms by simply adding different input modes. For example, if you require numeric data, such as credit card numbers, you can include Inputmode, numeric. This makes it easier for the user to add numbers. The same is true for email, inputMode =email.

An inputmode available value is none, text, tel, url, E-mail, numeric, decimal, and search. For more examples, check out inputmodes.com (preferably on mobile devices).

Done automatically

With an inputmode, attributes the [autocomplete] (https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) is a built-in function, Can greatly improve the user experience of your forms. Many web sites use forms to ask users for the same information: email, address, phone number, credit card, etc. One nice feature built into the browser is the ability for users to save their own information, which can be automated across different forms and websites. Autocomplete allows us to take advantage of this feature.

The autocomplete property is valid for any text or numeric input as well as for

elements. There are too many available values for me to list here, but some standouts are: current-password,one-time-code,street-address, CC-number (and various other credit card options), and TEL.

Providing these options can lead to a better experience for many users. And there’s no need to worry that this is a security issue, because the information only exists on the user’s machine, and they must allow their browser to implement it.

autofocus

I will mention the last a built-in attribute is [an autofocus] (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#htmlattrdefautofocus). By adding it to the input, the browser focuses on the input, selection, or text area (Chrome also supports using it on

A word of caution, though. Not every form is suitable for Autofocus. Placing autofocus on an element will scroll to that element. So if there’s something else on the page, we might scroll through all of it. This can be a particularly jarring experience for users who rely on assistive technologies such as screen readers. Use this feature only when it truly improves the experience for all users.

Automatically expanded text area

A very small feature, but one THAT I really appreciate is a Textarea that automatically expands to match the content in it. This way you don’t have to deal with large areas of text, or areas that are too small to handle with a scroll bar. This may not be the right feature for every use case, but it does add some sparkle to some forms. There is a naive implementation here.

JSON

textarea.addEventListener('input', () => {
  textarea.style.height = "";
  textarea.style.height = Math.min(textarea.scrollHeight, 300) + "px";
});
Copy the code

I call this a naive implementation because, in my experience, it’s hard to come up with a one-size-fits-all solution because different sites have different CSS rules applied to text areas. Sometimes it’s the padding or border-width, sometimes it’s the box-sizing attribute. In any case, you can use this as a starting point or, of course, reach out for a library.

Disables the scrolling event for digital input

If you’re not familiar with that, there’s a browser feature on number input that allows you to use the mouse wheel to increase or decrease numbers. This can be a great feature if you need to change values quickly and don’t want to type. However, this feature can also lead to errors, because on long pages that need to be scrolled, users sometimes accidentally reduce their input when their intention is to scroll down the page. There is a simple enough solution.

JSON

<input type="number" onwheel="return false;" />
Copy the code

By adding the [onwheel] (https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event) event handlers, We’re basically telling the browser to ignore this event (but it still fires any additional wheel events). So if we’re dealing with numbers like addresses, zip codes, phone numbers, social security, credit cards, or any other number that obviously doesn’t need to be incremented or decrement, we can use this handy fragment. In these cases, however, I would probably recommend using text input instead.

validation

Validation is when you take form data and make sure it matches the format you are looking for. For example, if you want someone to submit an email on a form, you need to verify that it contains an @ symbol. There are many different types of validation, and many ways to do it. Some validation takes place on the client side and some on the server side. Let’s look at some “shoulds” and “don ‘ts”.

Delay validation to obfuscate or commit events

With HTML5, it’s easy to add some client-side validation to your forms. You can decide to enhance it with some JavaScript, but when you choose to validate input is important.

Suppose you have a function that accepts an input of the DOM node, check its [ValidityState] (https://developer.mozilla.org/en-US/docs/Web/API/ValidityState), And switch a class if it is valid or not.

JSON

function validate(input) {
  if (input.validity.valid) {
    input.classList.remove('invalid')
  } else {
    input.classList.add('invalid')
  }
}
Copy the code

You must choose when to run the function. It could be any time the user clicks input, presses a key, leaves input, or submits a form. My suggestion is reserved for the following two cases verify events [the blur] (https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) (when an input loses focus) or in the form [submit] (https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event). Validation on the initial focus seems inappropriate, and it can be annoying to validate on the keys. It’s like someone trying to correct you before you finish your comment.

For the most part, I like to tie my validation logic to the commit event. I think this simplifies things and maintains a more cohesive experience in cases where I want some server-side validation logic. That said, the Blur event is also a handy place to validate.

Do not hide validation criteria

Another useful, if not obvious, hint is to clearly tell the user up front what makes an input valid or invalid. By sharing this information, they already know that their new password needs to be eight characters long, contain both upper and lower case letters, and include special characters. They don’t have to go through the steps of trying one password and being told to choose another.

I suggest there are two ways to achieve this. If it’s a basic format, you might want to use placeholder properties. For more complex things, I recommend putting the requirements in plain text, immediately below the input, and including an aria-Labelledby attribute on the input so that the requirements are also passed on to assistive technology users.

Send back all server validation errors at once

Another annoying experience users have when filling out forms is having to resubmit the same form multiple times because some of the data is invalid. This may be because the server validates one field at a time and returns an error immediately, or because there are multiple validation criteria for an input, but the server returns a validation error as soon as it encounters the first validation criterion rather than catching every error.

As an example, suppose I have a registry that requires my email and a password of at least eight characters, at least one letter and at least one number. Worst-case scenario, if I don’t know the situation, I may have to resubmit the form multiple times.

  • Error because I did not include an email
  • Wrong, because my password is too short
  • Wrong, because my password needs to include letters
  • Error, because my password needs to include numbers
  • Success!

As developers writing forms, we don’t always have control over the logic behind the scenes, but if we do, we should try to provide all errors as a single message.” The first input must be an E-mail. The password must be 8 characters long. Contains only letters and digits. The password must contain one letter and one number. Or words to that effect. The user can then resolve all errors at once and resubmit.

submit

Use JavaScript to submit

No matter what you think about the explosive growth of JavaScript in every part of our lives, there’s no denying that it’s a useful tool for making the user experience better. Tables are a perfect example. Instead of waiting for the browser to submit the form, we can use JavaScript to avoid page reloading.

To do this, we add an event listener to the event. [submit] (https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event), Capture the input value of the form by passing the form (event.target) to an event listener. [FormData] (https://developer.mozilla.org/en-US/docs/Web/API/FormData), and sends the data to the target URL (form. The action), Use [fetch] (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and at the same time [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams).

JavaScript

function submitForm(event) {
  const form = event.target
  const formData = new FormData(form)
  
  fetch(form.action, {
    method: form.method,
    body: new URLSearchParams(formData)
  })
  
  event.preventDefault()
}

document.querySelector('form').addEventListener('submit', submitForm)
Copy the code

The event.preventDefault() at the end of the handler prevents the browser from submitting the event by default via HTTP requests. This leads to page reloads and is not a good experience. One key issue is that we put this method last, just in case we get an exception somewhere in the handler and our form still falls back to the HTTP request and the form is still submitted.

Including status indicators

This tip is closely related to the previous tip. If we submit the form in JavaScript, we need to update the user’s submission status. For example, when the user clicks the submit button, there should be some indication (preferably visual and non-visual) that the request has been sent. In fact, we have four states to illustrate it.

  • Before the request is sent (perhaps there is no special need here
  • Request to be determined
  • A successful response was received
  • Failed response received. Procedure

There are too many possibilities for me to tell you exactly what you need in your situation, but the point is that you remember to explain all of them. Don’t leave the user wondering if there was an error in the sent request. (That’s a quick way to get them to mess with the submit button.) Don’t assume that every request will be successful. Tell them there has been a mistake and, if possible, how to fix it. When their request is successful, give them some confirmation.

Scrolling error

In the case of your form, it’s a good idea to let the user know exactly what went wrong (as we saw above) and where it went wrong. Especially on long scrolling pages, your users might try to submit a form with some kind of error, and even if you color the input red and add some validation error messages, they might not see it because it’s not in the same part of the screen they’re in.

Again, JavaScript can help us here by searching for the first invalid input element in the form and paying attention to it. The browser automatically scrolls to any element that receives focus, so with very little code you can provide a much better experience.

JavaScript

function focusInvalidInputs(event) => { const invalidInput = event.target.querySelector(':invalid') invalidInput.focus()  event.preventDefault() } document.querySelector('form').addEventListener('submit', focusInvalidInputs)Copy the code

User experience is a very subjective thing, and this list is not intended to be complete, but I hope it will provide you with some concepts and patterns to improve your forms.