A complete guide to the HTML number input


Removing the increment/decrement buttons

Desktop browsers display tiny up and down arrow buttons called spinners (in Chrome you need to hover the input to see them).

Should you wish to, removing these buttons takes a few lines of CSS:


/* For Chrome/Edge and Safari */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
    appearance: none;
}

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

The appearance property has not required a vendor prefix in any browser for several years but Chrome and Safari do still require non-standard ::-webkit- prefixed selectors.

The buttons cannot be styled with CSS, but that might change in the future. It is, however, easy to implement your own increment and decrement buttons.


Number inputs do not support the pattern attribute. Instead we can work with the min, max and step attributes.

The step attribute

The step attribute specifies the interval between allowed values. It defines the granularity of allowed numbers — essentially, how much the value should increase or decrease each time it’s adjusted. This applies both to user interactions (like clicking the spinner buttons or pressing arrow keys) and to validation.

By default, <input type="number"> only accepts whole numbers. If you don’t specify a step value, the browser treats it as if step="1", meaning decimal values aren’t considered valid.

In the following example, up to two decimal places are valid, which is useful for monetary values:

<input type="number" step="0.01" />

Specifying an initial value or min attribute sets the starting-off point of the steps. For example, if the starting value is 1.5 and the step is 1, then only values like 0.5, 2.5, 3.5, and so on are considered valid.

<input type="number" value="1.5" />

step="any" removes all restrictions, allowing any numeric value — including any amount of decimal places — instead of fixed increments.

Custom increment and decrement buttons using the stepUp() and stepDown() methods

Incrementing and decrementing a value with JavaScript is trivial, but we need to do so in a way that respects the min, max and step attributes. The stepUp() and stepDown() methods make this simple.

This might be a good use case for defining custom invoker commands:

const numberInputs = document.querySelectorAll('input[type="number"]');
numberInputs.forEach((input) => {
    input.addEventListener("command", (event) => {
        if (event.command === "--increment") {
        input.stepUp();
        } else if (event.command === "--decrement") {
        input.stepDown();
        }
    });
    });
<button commandfor="cost" command="--decrement">-</button>
<input id="cost" type="number" min="0.01" max="10" step="0.01" />
<button commandfor="cost" command="--increment">+</button>

Scroll to increment/decrement

One perhaps unexpected feature of the number input tripped some users up: in desktop Safari, focusing the input and then scrolling with a mouse would increase or decrease the value. While some people may have found this functionality useful, it could lead to users inadvertently changing the value when they were trying to scroll the page. This behaviour was removed in Safari 18.4. Chrome has this functionality but only when an onwheel event listener is set e.g. <input type="number" onwheel />.

Numeric keyboard on mobile

The default keyboard shown on iOS and iPadOS includes more than just numbers. This can be controlled via the HTML inputmode attribute. For a numbers-only keyboard, specify inputmode="numeric":

<input type="number" inputmode="numeric" />

For a keyboard that includes only numbers and a decimal point, use inputmode="decimal":

<input type="number" inputmode="decimal" step="any"/>

Allowed characters on desktop

inputmode="numeric" prevents users from entering anything other than whole numbers on mobile, but has no effect for users with a physical keyboard. What happens when a user types a non-numeric character? Browsers are inconsistent. In Firefox and older versions of Safari, users can type whatever they want. Safari 26.2 updated the behaviour to match Chromium-based browsers. In Chrome/Edge and recent versions of Safari, disallowed characters are immediately and silently rejected: if a user types a disallowed character, it doesn’t show up in the input. What characters are allowed? In all browsers a decimal point, the plus and minus symbols and the letter e can be typed into the input. There is no way to change this behaviour, other than via JavaScript. Notably, commas aren’t valid, so users can’t use them as delimiters when typing large numbers.

.valueAsNumber

The .valueAsNumber property provides the value as a number rather than a string, which saves you needing to use parseInt or parseFloat.

const input = document.querySelector('input[type="number"]');
const number = input.valueAsNumber;

When the number input contains a non-numeric value and you retrieve the .value in JavaScript, you get an empty string

If the value entered by the user is not a number, .value will return an empty string "" (whereas .valueAsNumber will return NaN) and there is no way to obtain the string that the user typed. Some developers seem to think this is a big deal. The article Why the number input is the worst input on the Stack Overflow blog argues: “If you are building a form that requires conditional validations or calculations, it is hard to understate just how big of a problem this is… getting a blank string makes the kind of validation I routinely get asked to deliver impossible”. As we’ll see, validation is actually fairly straightforward.

Validation

Validation in JavaScript

Giving users feedback when they fill out a form is an essential aspect of UX. The ValidityState interface provides a whole host of useful information when validating the number input via JavaScript.

  • badInput returns true if the value is not a number.
  • rangeUnderflow returns true if the number is less than the minimum specified by the min attribute.
  • rangeOverflow returns true if the number is greater than the maximum specified by the max attribute.
  • stepMismatch returns true if the value does not conform to the constraint set by the step attribute. If the step attribute is omitted, returns true if the value is not an integer.
  • valueMissing returns true if the field is required and is empty.
  • valid returns true if the value is a number and meets all requirements specified by the min, max and step attributes. If the required attribute is omitted then an empty input will also return true.
const input = document.querySelector('input[type="number"]');
const feedback = document.getElementById("msg");

input.addEventListener("input", function (event) {
    if (input.validity.badInput) {
        feedback.textContent = `Value must be a number`;
    } else if (input.validity.rangeOverflow) {
        feedback.textContent = `Value must be below ${input.max}`;
    } else if (input.validity.rangeUnderflow) {
        feedback.textContent = `Value must be above ${input.min}`;
    } else if (input.validity.stepMismatch) {
        feedback.textContent = `Value must be a whole number`;
    } else if (input.validity.valueMissing) {
        feedback.textContent = `Required field cannot be empty.`;
    } else if (!input.validity.valid) {
        feedback.textContent = `Value is invalid in some inexplicable way. ${input.validationMessage}`;
    } else {
        feedback.textContent = " ";
    }
});

Styling error states

Like other inputs, the number input can be styled with :valid, :invalid, :user-valid and :user-invalid pseudo-classes. Additionally, there’s both an :in-range and :out-of-range pseudo-class to indicate whether the value is within the limits specified by the min and max attributes. It’s important to alert users to all errors, so I can’t think of a good use case for :in-range or :out-of-range. Unlike rangeOverflow and rangeUnderflow in JavaScript, there’s no way to discern between whether the value is too high or too low in CSS.

When not to use <input type="number" />?

As demonstrated, its easy to use CSS to remove the increment and decrement buttons from a number input. However, a user can still use the up and down arrow buttons on their physical keyboard to change the value. This makes the number input inappropriate for fields like credit card numbers, ZIP codes, social security numbers, or one-time passcodes, where <input type="text" inputmode="numeric" /> should be used instead, together with an appropriate pattern attribute. If the value could potentially start with a leading 0, do not use <input type="number" />.

Conclusion

There are a few anti <input type="number" /> articles around the web but they’re mostly out of date. Why the GOV.UK Design System team changed the input type for numbers, published in 2020, relates all the reasons that team stopped using <input type="number" />. The post listed several accessibility-related bugs, all of which have since been fixed 🎉. Today, there are few reasons to avoid <input type="number"/>.