Finger-friendly numerical inputs with `inputmode`

Avatar of Ollie Williams
Ollie Williams on (Updated on )

Forms are often a nightmare on mobile. We can make the process as pain-free as possible by reacting to context. Input fields that expect numerical values should have a numerical UI. Bringing up a number keyboard on small screens is easy on most platforms — just use a <input type="number">.

A screenshot of the iOS number keyboard with keys for numbers one through ten.

This big button numeric keyboard is finger-friendly and will help prevent users bouncing from your form in frustration. However, type="number" isn’t appropriate for all numbers.

On (most) larger screens, number inputs come with an incrementer/decrementer button. It’s a useful piece of UI we get for free by default. It does, however, make this kind of input totally inappropriate for a credit card number, for example.

A default number input that displays the up and down arrows that allow users to increase and decrease numbers in the field.
The default UI for number inputs looks something like this in all desktop browsers

The spec itself makes this clear.

The type=number state is not appropriate for input that happens to only consist of numbers but isn’t strictly speaking a number. For example, it would be inappropriate for credit card numbers or US postal codes. A simple way of determining whether to use type=number is to consider whether it would make sense for the input control to have a spinbox interface (e.g., with “up” and “down” arrows). Getting a credit card number wrong by 1 in the last digit isn’t a minor mistake, it’s as wrong as getting every digit incorrect. So it would not make sense for the user to select a credit card number using “up” and “down” buttons. When a spinbox interface is not appropriate, type=text is probably the right choice (possibly with a pattern attribute).

It’s easy to hide the up and down buttons with CSS:

input[type="number"] {
  -moz-appearance: textfield;
}
input[type="number"]::-webkit-inner-spin-button, 
input[type="number"]::-webkit-outer-spin-button { 
  -webkit-appearance: none; 
  margin: 0; 
}

It’s important to note that this isn’t the only difference between a number and text input. You should definitely follow the spec on this point! Some older browsers will strip out leading zeros for number inputs which would be a big problem for US ZIP codes. The often-useful maxlength attribute is ignored on number inputs.

Why would anybody dislike such a useful input?

The answer comes down to validation and using the input for the wrong thing. The number input performs input sanitization by default. If a user enters anything that isn’t a valid number, the value will be equal to an empty string — regardless of what the user can see on the screen.

This input sanitization can trip developers up, and there’s no way to turn it off. If you want to allow input that isn’t a valid number, don’t use type="number".

A screenshot showing a number input that is filled with numbers and includes dashes between some of the numbers.
Number input in Chrome. This might be valid input for your use case, but it’s illegitimate in the eyes of the number input.
var numberinput = document.querySelector('input[type="number"]')
numberinput.value // will be ""

This might not be what you would intuitively expect. However, if you follow the spec and only use the number input for what its designed for — actual numbers — this behavior is unproblematic.

Number Input Alternatives

iOS Solution: Use the `pattern` Attribute on a Text Input

On iOS devices, using the pattern attribute with a value of [0-9]* will bring up the numeric keypad. This only works with this exact pattern — you can’t allow any extra characters.

<label for="creditcard">credit card number:</label> <input pattern="[0-9]*" type="text" name="creditcard">
The iOS number keyboard that displays only numbers one through ten and no other special characters.
iOS with pattern attribute

Bear in mind that an iPhone won’t let the user switch keyboard type when this keyboard is displayed. Make sure these are the only keys they need to fill in the input correctly.

If you want to bring up a keypad of large numeric keys on iOS, you need to use the pattern attribute even on number inputs. Otherwise, you’ll get small and finger-friendly buttons:

A screenshot of the iOS keyboard displaying both numbers and letters.
iOS keypad for <input type="number">

A Better Solution: `inputmode`

inputmode has been a WHATWG spec for a couple of years, and has finally been implemented by Chrome as of version 66:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
6695No79No

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
12312412312.2-12.5

For the widest support possible, it can be combined with the pattern attribute for iOS:

<label for="creditcard">credit card number:</label> <input inputmode="numeric" pattern="[0-9]*" type="text" name="creditcard">

This gives developers full control of the mobile UI without any extra baggage. It makes the UI finger-friendly while being more versatile than the pattern attribute as we can allow any characters we like. It controls one thing — and one thing only. inputmode is a great solution for those cases when it’s inappropriate to use type="number".

Some people would go further and ditch type="number" altogether once inputmode has better support. I’m not convinced that’s wise, but type="number" can be problematic.

if (numberInput.validity.valueMissing) {
errorMessage.textContent = "field must not be empty"
}
A screenshot of a number input filled with numbers and special characters with an error message below it that informs the user the field is empty.
Contrary to the human eye, the field is empty…

If you want to explicitly warn of empty number inputs, you’ll need to use:

if (numberInput.validity.valueMissing && !numberInput.validity.badInput) {
errorMessage.textContent = "field must not be empty"
}

According to Google, users abandon purchases twice as often on mobile as compared to desktop. Sales on phones account for only one third of all completed online purchases. Clearly people don’t tolerate fumbling through badly designed forms and jabbing at tiny inputs. Data entry needs to be effortless. While browser support is currently low, we’re only really waiting on mobile browsers. Desktop support is largely irrelevant. The input elements introduced by HTML5 are great, but they miss some edge cases. This can fill in some gaps.