I Wanted To Type a Number

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:

`type="text"` Keyboard on Android 6 (Samsung Galaxy S7)
`type="number"` Numeric Keyboard on Android 6 (Samsung Galaxy S7)

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:

  1. Prices (sometimes integers with thousands separators, but often used as non-integers)
  2. Zip Codes (in the US these can contain leading zeros)
  3. Credit Cards (very long numbers)
  4. Credit Card Security Codes: CVV, CVC, or CSC (may also have leading zeros)
  5. Gift cards (formats vary)
  6. Ages
  7. Years
  8. Times (Minutes or Seconds may have leading zeros)
  9. 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

Permalink to '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.

`type="text"` Keyboard on Android 6 (Samsung Galaxy S7)

Useful Pragmatism

Permalink to '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"

Permalink to '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".

Try it in your browser

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.

No decimal point: Android 2.3 (Samsung Galaxy S2) with type="number"
Looks like a telephone input: Android 2.3 (Motorola Droid Razr) with type="number"
No negative sign: Android 6 (Samsung Galaxy S7) with type="number"

Perhaps unsurprisingly to veteran web developers, Firefox is closest to the specification here, with reliable, dedicated buttons for both decimal points and negative signs.

Looks great: Firefox 47 on Android 5.1.1 (Motorola Nexus 6) with type="number"

Windows Phone has no support for negative numbers on their type="number" keyboard either.

No negative sign: Windows Phone 8.1 (Lumia 930) with type="number"

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.

Small buttons: iOS 9 with type="number"

The pattern Attribute

Permalink to '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.

Looks like a telephone input: iOS 9 (on an iPhone) with type="text" pattern="[0-9]*

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.

Try it in your browser

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.

Try it in your browser

type="tel"

Permalink to '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.

Try it in your browser

There has to be a better way

Permalink to '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.

Try it in your browser

Working around our Limitations

Permalink to '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

Permalink to '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

Permalink to '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

Permalink to '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

Permalink to '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

Permalink to '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.

Spinbox arrows on Chrome with type="number"

Using numeric-input

Permalink to '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

Permalink to 'A few caveats'

Scientific Notation not supported

Permalink to '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

Permalink to '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.

All blog posts