Mark Hall's Webdevblog
Creating a Perfect HTML Form FieldPosted on

On one side is your web server. On the other side are a bunch of human beings. What is the interface between those sides to gather information from these humans? HTML forms.

It's easy enough to create an HTML form and hook it up to a backend script to process the data, but from a usability standpoint, there are many things to consider to make your form fields as quick and easy to use as possible. Around half of your traffic is likely to be on mobile devices as well. Since filling out fields on phones is much slower than with a mouse and keyboard, these days it's more important than ever that your form fields are optimized.

Let's go from the simplest possible starting point...

HTML
<form id="my-form" action="somewhere" method="post"> <div> Telephone: <input type="text" name="phone"> </div> <input type="submit" value="Send"> </form>

A form with a single field and a submit button. Simple enough, but we can do better. First things first, we need to properly label it. So give the phone number field an id and set up a label to it:

HTML
<form id="my-form" action="somewhere" method="post"> <div> <label for="phone-field">Telephone:</label> <input type="text" name="phone" id="phone-field"> </div> <input type="submit" value="Send"> </form>

Now when you click or tap on the label, the text box will focus. This also affects accessibility, as screen readers will associate the label with its field. This will also help with styling; if you want to have all your field labels the same width and style in the CSS, you can use label as a selector.

Now, we can do a couple of things to make the field itself better. Depending on the field, you may want to add a placeholder attribute to give the user an idea of the format, or an example value. This field is a phone number field, so I'll make it have a placeholder showing xxx-xxx-xxxx.

The other thing to look at is whether there exists a special HTML input type for the field. In this case, we can use type="tel" instead of the default type="text". The difference will be most noticeable on mobile devices. When the user focuses the field, it will pop up a big easy to use numpad instead of the whole keyboard. If you use type="email" then they'll get a slightly modified keyboard that's easier to enter email addresses with, plus some automatic email format validation on submit. And if any device or browser doesn't support the field type, it will gracefully fall back to a standard text box, which isn't too bad. So here we are with our new, upgraded telephone field:

HTML
<form id="my-form" action="somewhere" method="post"> <div> <label for="phone-field">Telephone:</label> <input type="tel" name="phone" placeholder="xxx-xxx-xxxx" id="phone-field"> </div> <input type="submit" value="Send"> </form>

Now, I'm assuming that the action target is a backend script that will validate the field and return an error message if something's wrong with it. No problem with that if it's just one field, but this is a minimal example. Your form probably has a bunch of fields and it's not very helpful to see all the errors at the top, and have to scan through the form to find out where each error message is coming from.

We'll want the error message to pop up somewhere around the field. Maybe just under it. We'll need a combo of JavaScript and CSS to get this working. First, let's make a div for the error notification. Add some CSS to make it display: none by default when it's empty.

Last part is changing the submit button to a regular button that we can attach a JavaScript event to.

CSS
.error-notify { color: #ff0000; font-weight: bold; display: block; } .error-notify:empty { display: none; }
HTML
<form id="my-form" action="somewhere" method="post"> <div> <label for="phone-field">Telephone:</label> <input type="tel" name="phone" placeholder="xxx-xxx-xxxx" id="phone-field"> <div class="error-notify" id="phone-error"></div> </div> <input type="button" id="submit-me" value="Send"> </form> <script src="validation.js"></script>

Now onto the JavaScript which will tie this all together.

The validation can be done with regular JavaScript, but you're going to need server-side validation for security reasons, since any client-side validation can be bypassed by a malicious user. So what the form really should do is send the data with AJAX and handle the response to either pop up error messages or show them the result of their submission.

There are a lot of different ways to do this, but the way I prefer is for the backend code to return a JSON-encoded associative array for all errors, and then parse through the array to insert the error message via the textContent attribute of the error notification.

Here's what a response from the server-side validation might look like:

JSON
{ "response": "ERROR", "errors": { "phone": "Phone number is invalid" } }

I'll leave the backend code out of this tutorial since it could be in any language. We'll just deal with the response.

We'll use addEventListener to attach the code to the submit button. Then we'll use the fetch API to do an AJAX post to the backend validation. The values that we are sending to the backend can be encoded into the URL to fetch using URLSearchParams.

The code will start by clearing all the fields' error messages, so we don't have a lingering error message from a previous attempt.

Here is the code for validation.js

JavaScript
document.getElementById('submit-me').addEventListener("click", () => { let params = new URLSearchParams(); let fieldlist = [ "phone" ]; for (let i of fieldlist) { document.getElementById(i + "-error").textContent = ""; // Clear existing error messages params.set(i, document.getElementById('my-form').elements[i].value); // There's more involved in getting values from checkboxes, radio buttons, and select menus // But I'll cover that another day } fetch("somewhere", { method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded"}, body: params.toString() }).then(response => response.json()).then(data => { if (data.response == "APPROVED") alert("Form fields are valid. Do something cool here!"); else { for (let i in data.errors) { // Populate error message for a field document.getElementById(i + "-error").textContent = data.errors[i]; } } }); });

So there we are, a full-featured, accessible form field with AJAX validation.

There are many other things you can consider to make it better, depending on the purpose of the form. Here are some ideas:

  • Have the validation run for each field individually as you type. You can modify your backend validation to do single field validations, and attach a call to that validation to the field's input event.
  • Use background colors in the form field. Typically red is associated with errors and green is valid input.
  • Use responsive CSS to have the labels above the fields on smaller screens and beside the fields on wider screens.
  • Add some JavaScript that automatically formats data as the user types, such as automatically adding dashes between parts of a phone number.
  • Make detailed error messages for certain types of errors that you think people might make on a form so they can more quickly determine how to correct their data.
  • If the form is long, make it automatically scroll to the first validation error message if there are errors.
  • Add a datalist to any text field where you know many of the responses will be within a set list of options. This helps the user to fill it out quickly with an added side benefit of normalizing the data.
  • Add an HTML reset button so that the user can accidentally click it instead of the submit button, clear all their data and have to start again.

Okay, maybe don't do that last one.