Skip to main content

Number-only inputs aren't so straight-forward

Summary: While type="number" can come in handy, it's far from perfect, and in those cases where it lacks, inputmode="numeric" can come in handy.

Kevin Powell

This post was based on this YouTube Short

If you’ve ever needed to create an input that should only be a number, I bet you’ve used input type="number".

This is good because it changes the keyboard on mobile devices to make things a bit easier… but it’s far from perfect.

First off, on desktop, you get these little up-and-down arrows.

<input type="number" />
a number input on desktop, with up and down arrows on the right side

I guess someone could use those to eventually get to their credit card number, but I wouldn’t really say they’re the best of user experiences.

And speaking of user experience, before we fix those up-and-down arrows, we can improve the mobile keyboards with an inputmode="numeric" which gives us only numbers on iOS (Android already has a pretty paired down keyboard).

<input type="number" inputmode="numeric" />
three different iOS keyboards, from a regular type=text, then type=number, followed by inputmode=numeric

Circling back to those up-and-down arrows we have two options:

We can remove them with some this CSS:

/* Chromium & Safari */
input[type="number"]::-webkit-inner-spin-button {
  display: none;
}

/* Firefox */
input[type="number"] {
  -moz-appearance: textfield;
}

Or we could switch our input back to an input type="text" but keep the inputmode on there, which keeps the keyboard numbers-only on mobile devices.

Then, we can add a pattern to that input so only numbers are considered valid.

<input type="text" pattern="[0-9]+" inputmode="numeric" />

You might be thinking that input type="number" prevents users from entering other characters, but Chrome is the only browser to do that, so if you want to prevent that from happening, you’d need just as much JS in either case.

const numericInputs = document.querySelectorAll("[inputmode='numeric']");

numericInputs.forEach((input) => {
  validateInput(input);
});

function validateInput(el) {
  el.addEventListener("beforeinput", function (e) {
    let beforeValue = input.value;
    e.target.addEventListener(
      "input",
      function () {
        if (input.validity.patternMismatch) {
          input.value = beforeValue;
        }
      },
      { once: true }
    );
  });
}

Here is a working example with a few of these in place if you want to play around with them a little bit.

See the Pen inputmode numeric by Kevin (@kevinpowell) on CodePen.

Link copied to clipboard