Forms are hard to fill out on mobile. The best forms are the ones with the fewest inputs. Good forms provide semantic input types. Keys should change to match the user's input type; users pick a date in a calendar. Keep your user informed. Validation tools should tell the user what they need to do before submitting the form.
Design efficient forms
Design efficient forms by avoiding repeated actions, asking for only the necessary information and guide users by showing them how far along they are in multi-part forms.
TL;DR
- Use existing data to pre-populate fields and be sure to enable auto-fill.
- Use clearly-labeled progress bars to help users get through multi-part forms.
- Provide visual calendar so users don’t have to leave your site and jump to the calendar app on their smartphones.
Minimize repeated actions and fields

Make sure your forms have no repeated actions, only as many fields as necessary, and take advantage of autofill, so that users can easily complete forms with pre-populated data.
Look for opportunities to pre-fill information you already know, or may anticipated to save the user from having to provide it. For example, pre-populate the shipping address with the last shipping address supplied by the user.
Show users how far along they are

Progress bars and menus should accurately convey overall progress through multi-step forms and processes.
If you place a disproportionately complex form in an earlier step, users are more likely to abandon your site before they go through the entire process.
Provide visual calendars when selecting dates

Users often need more context when scheduling appointments and travel dates, to make things easier and prevent them from leaving your site to check their calendar app, provide a visual calendar with clear labeling for selecting start and end dates.
Choose the best input type
Streamline information entry by using the right input type. Users appreciate websites that automatically present number pads for entering phone numbers, or automatically advance fields as they entered them. Look for opportunities to eliminate wasted taps in your forms.
TL;DR
- Choose the most appropriate input type for your data to simplify input.
- Offer suggestions as the user types with the
datalist
element.
HTML5 input types
HTML5 introduced a number of new input types. These new input types give hints to the browser about what type of keyboard layout to display for on-screen keyboards. Users are more easily able to enter the required information without having to change their keyboard and only see the appropriate keys for that input type.
Input type |
|
---|---|
url For entering a URL. It must start with a valid URI scheme, for example http:// , ftp:// or mailto: .
|
![]() |
tel For entering phone numbers. It does not enforce a particular syntax for validation, so if you want to ensure a particular format, you can use pattern. |
![]() |
email For entering email addresses, and hints that the @ should be shown on the keyboard by default. You can add the multiple attribute if more than one email address will be provided. |
![]() |
search A text input field styled in a way that is consistent with the platform's search field. |
![]() |
number For numeric input, can be any rational integer. Additionally, iOS requires using pattern="\d*" to show the numeric keyboard.
|
![]() |
range For number input, but unlike the number input type, the value is less important. It is displayed to the user as a slider control. |
![]() |
datetime-local For entering a date and time value where the time zone provided is the local time zone. |
![]() |
date For entering a date (only) with no time zone provided. |
![]() |
time For entering a time (only) with no time zone provided. |
![]() |
week For entering a week (only) with no time zone provided. |
![]() |
month For entering a month (only) with no time zone provided. |
![]() |
color For picking a color. |
![]() |
Offer suggestions during input with datalist
The datalist
element isn't an input type, but a list of suggested input values
to associated with a form field. It lets the browser suggest autocomplete
options as the user types. Unlike select elements where users must scan long
lists to find the value they're looking for, and limiting them only to those
lists, datalist
element provides hints as the user types.
<label for="frmFavChocolate">Favorite Type of Chocolate</label> <input type="text" name="fav-choc" id="frmFavChocolate" list="chocType"> <datalist id="chocType"> <option value="white"> <option value="milk"> <option value="dark"> </datalist>
Label and name inputs properly
Forms are hard to fill out on mobile. The best forms are the ones with the fewest inputs. Good forms provide semantic input types. Keys should change to match the user's input type; users pick a date in a calendar. Keep your user informed. Validation tools should tell the user what they need to do before submitting the form.
TL;DR
- Always use
label
s on form inputs, and ensure they're visible when the field is in focus. - Use
placeholder
s to provide guidance about what you expect. - To help the browser auto-complete the form, use established
name
's for elements and include theautocomplete
attribute.
The importance of labels
The label
element provides direction to the user, telling them what
information is needed in a form element. Each label
is associated with an
input element by placing it inside the label
element, or by using the "for
"
attribute. Applying labels to form elements also helps to improve the touch
target size: the user can touch either the label or the input in order to place
focus on the input element.
<label for="frmAddressS">Address</label> <input type="text" name="ship-address" required id="frmAddressS" placeholder="123 Any Street" autocomplete="shipping street-address">
Label sizing and placement
Labels and inputs should be large enough to be easy to press. In portrait viewports, field labels should be above input elements, and beside them in landscape. Ensure field labels and the corresponding input boxes are visible at the same time. Be careful with custom scroll handlers that may scroll input elements to the top of the page hiding the label, or labels placed below input elements may be covered by the virtual keyboard.
Use placeholders
The placeholder attribute provides a hint to the user about what's expected in the input, typically by displaying the value as light text until the the user starts typing in the element.
<input type="text" placeholder="MM-YYYY" ...>
Use metadata to enable auto-complete
Users appreciate when websites save them time by automatically filling common fields like names, email addresses and other frequently used fields, plus it helps to reduce potential input errors -- especially on virtual keyboards and small devices.
Browsers use many heuristics to determine which fields they can
auto-populate
based on previously specified data by the user,
and you can give hints to the browser by providing both the name
attribute
and the autocomplete
attribute on each input element.
For example, to hint to the browser that it should auto-complete the form with the users name, email address and phone number, you should use:
<label for="frmNameA">Name</label> <input type="text" name="name" id="frmNameA" placeholder="Full name" required autocomplete="name"> <label for="frmEmailA">Email</label> <input type="email" name="email" id="frmEmailA" placeholder="name@example.com" required autocomplete="email"> <label for="frmEmailC">Confirm Email</label> <input type="email" name="emailC" id="frmEmailC" placeholder="name@example.com" required autocomplete="email"> <label for="frmPhoneNumA">Phone</label> <input type="tel" name="phone" id="frmPhoneNumA" placeholder="+1-555-555-1212" required autocomplete="tel">
Recommended input name
and autocomplete
attribute values
autocomplete
attribute values are part of the current WHATWG HTML Standard. The most commonly used autocomplete
attributes are shown below.
The autocomplete
attributes can be accompanied with a section name, such as shipping
given-name
or billing
street-address
. The browser will auto-complete different sections separately, and not as a continuous form.
Content type | name attribute |
autocomplete attribute |
---|---|---|
Name |
name
fname
mname
lname
|
|
email |
email |
|
Address |
address
city
region
province
state
zip
zip2
postal
country
|
|
Phone |
phone
mobile
country-code
area-code
exchange
suffix
ext
|
tel |
Credit Card |
ccname
cardnumber
cvc
ccmonth
ccyear
exp-date
card-type
|
|
Usernames |
username
|
|
Passwords |
password
|
|
The autofocus
attribute
On some forms, for example the Google home page where the only thing you want
the user to do is fill out a particular field, you can add the autofocus
attribute. When set, desktop browsers immediately move the focus to the input
field, making it easy for users to quickly begin using the form. Mobile
browsers ignore the autofocus
attribute, to prevent the keyboard from randomly
appearing.
Be careful using the autofocus attribute because it will steal keyboard focus and potentially preventing the backspace character from being used for navigation.
<input type="text" autofocus ...>
Provide real-time validation
Real-time data validation doesn't just help to keep your data clean, but it also helps improve the user experience. Modern browsers have several built-in tools to help provide real-time data validation and may prevent the user from submitting an invalid form. Visual cues should be used to indicate whether a form has been completed properly.
TL;DR
- Leverage the browser's built-in validation attributes like
pattern
,required
,min
,max
, etc. - Use JavaScript and the Constraints Validation API for more complex validation requirements.
- Show validation errors in real time, and if the user tries to submit an invalid form, show all fields they need to fix.
Use these attributes to validate input
The pattern
attribute
The pattern
attribute specifies a regular expression
used to validate an input field. For example, to validate a US Zip code
(5 digits, sometimes followed by a dash and an additional 4 digits), we would
set the pattern
like this:
<input type="text" pattern="^\d{5,6}(?:[-\s]\d{4})?$" ...>
Common regular expression patterns
Regular expression | |
---|---|
Postal address | [a-zA-Z\d\s\-\,\#\.\+]+ |
Zip Code (US) | ^\d{5,6}(?:[-\s]\d{4})?$ |
IP Address (IPv4) | ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ |
IP Address (IPv6) | ^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$ |
IP Address (both) | ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$ |
Credit Card Number | ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})$ |
Social Security Number | ^\d{3}-\d{2}-\d{4}$ |
North American Phone Number | ^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$ |
The required
attribute
If the required
attribute is present, then the field must contain a value before
the form can be submitted. For example, to make the zip code required, we'd
simply add the required attribute:
<input type="text" required pattern="^\d{5,6}(?:[-\s]\d{4})?$" ...>
The min
, max
and step
attributes
For numeric input types like number or range as well as date/time inputs, you can specify the minimum and maximum values, as well as how much they should each increment/decrement when adjusted by the slider or spinners. For example, a shoe size input would set a minimum size of 1 and a maximum size 13, with a step of 0.5
<input type="number" min="1" max="13" step="0.5" ...>
The maxlength
attribute
The maxlength
attribute can be used to specify the maximum length of an input or
textbox and is useful when you want to limit the length of information that the
user can provide. For example, if you want to limit a filename to 12 characters,
you can use the following.
<input type="text" id="83filename" maxlength="12" ...>
The minlength
attribute
The minlength
attribute can be used to specify the minimum length of an input or
textbox and is useful when you want to specify a minimum length the user must
provide. For example, if you want to specify that a file name requires at least
8 characters, you can use the following.
<input type="text" id="83filename" minlength="8" ...>
The novalidate
attribute
In some cases, you may want to allow the user to submit the form even if it
contains invalid input. To do this, add the novalidate
attribute to the form
element, or individual input fields. In this case, all pseudo classes and
JavaScript APIs will still allow you to check if the form validates.
<form role="form" novalidate>
<label for="inpEmail">Email address</label>
<input type="email" ...>
</form>
Use JavaScript for more complex real-time validation
When the built-in validation plus regular expressions aren't enough, you can use the Constraint Validation API, a powerful tool for handling custom validation. The API allows you to do things like set a custom error, check whether an element is valid, and determine the reason that an element is invalid:
Constraint Validation | |
---|---|
setCustomValidity() |
Sets a custom validation message and the customError property of the ValidityState object to true . |
validationMessage |
Returns a string with the reason the input failed the validation test. |
checkValidity() |
Returns true if the element satisfies all of its constraints, and false otherwise. Deciding how the page responds when the check returns false is left up to the developer. |
reportValidity() |
Returns true if the element satisfies all of its constraints, and false otherwise. When the page responds false , constraint problems are reported to the user. |
validity |
Returns a ValidityState object representing the validity states of the element. |
Set custom validation messages
If a field fails validation, use setCustomValidity()
to mark the field invalid
and explain why the field didn't validate. For example, a sign up form might
ask the user to confirm their email address by entering it twice. Use the blur
event on the second input to validate the two inputs and set the appropriate
response. For example:
if (input.value != primaryEmail) { // the provided value doesn't match the primary email address input.setCustomValidity('The two email addresses must match.'); console.log("E-mail addresses do not match", primaryEmail, input.value); } else { // input is valid -- reset the error message input.setCustomValidity(''); }
Prevent form submission on invalid forms
Because not all browsers will prevent the user from submitting the form if there
is invalid data, you should catch the submit event, and use the checkValidity()
on the form element to determine if the form is valid. For example:
form.addEventListener("submit", function(evt) { if (form.checkValidity() === false) { evt.preventDefault(); alert("Form is invalid - submission prevented!"); return false; } else { // To prevent data from being sent, we've prevented submission // here, but normally this code block would not exist. evt.preventDefault(); alert("Form is valid - submission prevented to protect privacy."); return false; } });
Show feedback in real-time
It's helpful to provide a visual indication on each field that indicates whether the user has completed the form properly before they've submitted the form. HTML5 also introduces a number of new pseudo-classes that can be used to style inputs based on their value or attributes.
Real-time Feedback | |
---|---|
:valid |
Explicitly sets the style for an input to be used when the value meets all of the validation requirements. |
:invalid |
Explicitly sets the style for an input to be used when the value does not meet all of the validation requirements. |
:required |
Explicitly sets the style for an input element that has the required attribute set. |
:optional |
Explicitly sets the style for an input element that does not have the required attribute set. |
:in-range |
Explicitly sets the style for a number input element where the value is in range. |
:out-of-range |
Explicitly sets the style for a number input element where the value is out of range. |
Validation happens immediately which means that when the page is loaded, fields may be marked as invalid, even though the user hasn't had a chance to fill them in yet. It also means that as the user types, and it's possible they'll see the invalid style while typing. To prevent this, you can combine the CSS with JavaScript to only show invalid styling when the user has visited the field.
input.dirty:not(:focus):invalid { background-color: #FFD9D9; } input.dirty:not(:focus):valid { background-color: #D9FFD9; }
var inputs = document.getElementsByTagName("input"); var inputs_len = inputs.length; var addDirtyClass = function(evt) { sampleCompleted("Forms-order-dirty"); evt.srcElement.classList.toggle("dirty", true); }; for (var i = 0; i < inputs_len; i++) { var input = inputs[i]; input.addEventListener("blur", addDirtyClass); input.addEventListener("invalid", addDirtyClass); input.addEventListener("valid", addDirtyClass); }