Posted by Zach on 07/13/2016
Forms are the bedrock upon which users interact with the web. For a long time, forms controls were stagnant and reliable. HTML5 brought us updates, specifying new <input>
types linked with new interface controls, progressively enhancing from the vanilla text inputs of yesteryear. The battle tested truth of new web features and standards proved very true with these new form controls: browser implementations vary.
This browser variability is particularly true with touch devices, especially in relation to the primary innovation of the touchscreen keyboard: its malleability, or adaptation its mode of input to best suit the context. For example, when a site specifies that the user should type a number, the browser can show a number-pad like keyboard (0 through 9) with extra large buttons for easier, faster, and more accurate numeric input. The usability difference between the small button and large button keyboards for numeric input is stark:
But how does the browser know when to show this improved numeric keyboard? How can a developer inform the browser of the user’s intent and context? Of course, variability exists there too.
Consider a few examples of the many different types of numeric input we may need to capture on our web forms:
- Prices (sometimes integers with thousands separators, but often used as non-integers)
- Zip Codes (in the US these can contain leading zeros)
- Credit Cards (very long numbers)
- Credit Card Security Codes: CVV, CVC, or CSC (may also have leading zeros)
- Gift cards (formats vary)
- Ages
- Years
- Times (Minutes or Seconds may have leading zeros)
- Phone Numbers
You might assume to just use <input type="number">
for all of the above. Unfortunately, that assumption would be wrong.
Let’s consult the Specification
Wait, come back! The HTML5 specification for input type="number"
will provide us with clues to what kind of input will be accepted by the control, so let’s level-set this thing.
The most important piece of this puzzle is the value which must be a Floating Point Number.
Floating Point Numbers are comprised of one or more characters in the range 0—9 (numbers, yeah?), optionally followed by a decimal point and more 0—9 characters. Floating Point Numbers may also have a leading -
to denote a negative number.
For simplification purposes note that I’ve omitted the scientific notation part of the specification, which we will try to avoid showing users as well (we’ll explain more about this later).
The important and problematic thing here is that values that technically obey the floating point specification definition above may work with <input type="number">
. Note the [last green note on the W3 HTML5 forms specification](https://www.w3.org/TR/html5/forms.html#number-state-(type=number)):
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).
This leaves us with some ambiguity. Minutes and Seconds fields are obvious counter-examples to the specification’s language. Spinbox would be valuable there, but those fields may need leading zeros.
From the notes in the specification, we can glean that type="number"
is intended for numbers like Prices, Ages, and Years; type="tel"
for Phone Numbers; type="text"
for everything else. This leaves a lot of numeric input fields with the undesirable small button, default type="text"
keyboard.
Useful Pragmatism
There very useful ways to opt-in to the big button numeric keyboard, available for use in browsers today:
type="number"
- The
pattern
attribute (iOS only) type="tel"
type="number"
We can choose between following the specification and having a sub-optimal keyboard for numeric input on a large swath of devices, or we can explore other options to make this work. In testing on Android (both Chrome and the older Android Browser), Firefox for Android, and Windows Phone, the only way to trigger a big button keyboard is to ignore the W3C specification and use type="number"
.
On the same Android operating system version Android big button numeric keyboard implementations vary—and many do not correctly implement the specification’s requirements for negative numbers and decimal points.
Perhaps unsurprisingly to veteran web developers, Firefox is closest to the specification here, with reliable, dedicated buttons for both decimal points and negative signs.
Windows Phone has no support for negative numbers on their type="number"
keyboard either.
On iOS, the type="number"
keyboard is different than type="text"
(and is safe to use per the specification: it has both keys for negative and decimal points) but is not the big button user experience we’re going for.
The pattern
Attribute
The big button numeric keyboard on iOS (on an iPhone) is shown when the pattern
attribute has the value [0-9]*
or d*
. iOS (on an iPhone) doesn’t care what the type
attribute value is. It could be text
, number
, et cetera. But if pattern="[0-9]*"
or pattern="d*"
, iOS (on an iPhone) will show a big button numeric keypad.
The big button numeric keyboard does not exist on the iPad variant of iOS—only on the iPhone version. With more screen real estate to work with on an iPad, using an approved pattern
value, type="tel"
, or type="number"
all use the small button numeric keyboard shown above.
It should be noted that functionally equivalent regular expressions with the same output do not show a numeric keyboard, e.g. [0123456789]*
or [0-9]{0,}
or [0123456789]{0,}
.
Logically because iOS (on an iPhone) ties this keyboard to a numbers-only pattern
regex, buttons for the negative sign or the decimal point simply don’t exist on the big button keyboard. Importantly, iOS (on an iPhone) does not let the user switch keyboard types when the big button numeric keyboard is displayed. Entering true floating point numbers is simply not allowed, per the pattern
explicitly defined.
Take care to also note that the pattern
attribute is overloaded to control HTML5 form validation. This forces developers to choose between a numeric keyboard or a more accurate validation pattern—e.g. a regular expression for a two digit number pattern="[0-9]{2}"
would not use the numeric keyboard.
Unfortunately, the pattern
trick for big button numeric keyboards only works on iOS. Other web browsers will display the default text
keyboard when the whitelist iOS pattern
attribute values are used, so we need to supplement this method with other techniques. Luckily, combining pattern
with type="number"
will trigger a big button numeric keypad on all the platforms we’ve discussed thus far.
type="tel"
If using type="number"
for some of these other free-form number formats goes against the spirit of the specification, using type="tel"
for things that are not phone numbers is a far more egregious semantic crime. I would not consider this a viable future-friendly alternative to type="number"
for numeric input.
There has to be a better way
Luckily, the web standards people have recognized this mess and have standardized an appetizing alternative: the inputmode
attribute. inputmode
lets you directly specify which type of keyboard to use, independent of the type
attribute value. For example, on a credit card number field we could use type="text"
and inputmode="numeric"
together—problem solved.
At time of writing, inputmode
is not supported anywhere. This is really unfortunate—it would alleviate a lot of the mismatched usage of <input type="number">
butting heads with the specification’s definition. This would help specifically on Android, Firefox on Android, and Windows Phone, which have all hitched big button numeric keyboards to type="number"
.
Side note: while inputmode="numeric"
is appetizing and would help with some of the issues, the specification continues to lack a provision for non-integers. Even after browser support for this feature burgeons we’ll still have to use type="text"
without inputmode
for non-integers given the unreliability of decimal point buttons on current numeric keyboards.
Working around our Limitations
As we explored this problem, it become clear that if we want to provide an efficient way to enter numbers across the broadest range of devices we need to be pragmatic and combine pattern
and type="number"
. Since we’re using type="number"
in more places than the specification may endorse, we must travel with great care and iron out wrinkles along the way.
We’ve wrapped what we learned into a small utility called numeric-input
which lets us use big button numeric keyboards today, while sidestepping some nasty bugs we ran into with numeric inputs. Here’s what it does:
Restrict to numbers only
Since this is an input designed to capture only numeric values, the numeric-input
plugin filters out any character that isn’t a 0-9 when you type, paste, or the value is auto-filled by the browser.
Note: This filtering means the “-” and “.” characters are stripped out so negative numbers or decimals aren’t supported with this plugin. Despite the fact that these are “numbers,” it’s best not to use numeric keyboards for these due to spotty support for negative number and decimal point buttons on numeric keyboards (as you can in the device screenshots above). Use type="text"
for these fields.
Support leading zeros
Leading zeros used in US Zip Codes or the minutes/seconds are dropped on blur in Safari 6). For example, a zip code would be changed from “01234” to “1234”. Our plugin simply toggles type="number"
fields to type="text"
in older Safari so the value is left alone. Luckily, the pattern
trick still ensures the large keypad on older iOS.
Prevent rounding on large numbers
Large numbers longer than 16 digits are rounded in Firefox (desktop, not Android or iOS). For example, el.value = "9999999999999999";
renders 10000000000000000
. This is a big problem for entering 16 digit credit cards so the plugin also toggles type="number"
fields to type="text"
in Firefox (Desktop) to work around this bug.
Support maxlength
If we want to enforce a specific value length (say 5 digits for a zip code), there isn’t a way to specify a maxlength="5"
with native numeric inputs. Sure, you can add a max="99999"
attribute but the plugin also adds support for maxlength
to enforce how many characters you want to accept. Works with keyboard entry, pasting, and auto-fill.
Remove Spinners & Arrow Keys
Desktop browsers have helpful features for incrementing or decrementing the value in a number field: adding small tiny up/down “spinner” arrows inside the fields, binding the keyboard’s arrow up/down keys, and mousewheel events. In some cases this behavior can be very destructive—think how unpredictable accidentally hitting the arrow key in a credit card field would be. numeric-input
includes an option (the data-numeric-input-nav-disabled
attribute) to disable increment and decrement arrow key behavior and the small spinbox arrows embedded sometimes shown inside the type="number"
inputs are easily hidden using CSS.
Using numeric-input
Using the numeric-input
plugin is pretty easy:
<input type="number" pattern="[0-9]*" data-numeric-input>
It’s only a small piece of a set of form utilities we’re calling formcore
which we’ll be adding more plugins to and documentating further in the future.
Try it in your browser
A few caveats
Scientific Notation not supported
Since the plugin strips out any letters, including the “e” character, support scientific and E notation isn’t supported. Use type="text"
for these situations.
Negative Numbers, Non-Integers
If the field requires negative numbers or decimals, there isn’t much numeric-input
can do to improve the spotty support for negative number and decimal point buttons on numeric keyboards. Use type="text"
for these situations.