SendForm.net

HTML Number Input: min, max, step and What Breaks

An HTML number input is <input type="number">: a field that accepts a numeric value, shows spinner arrows, and lets you set limits with min, max and step. It works well for quantities and amounts. It works badly for anything that only looks like a number, and several of its defaults surprise people.

Sophie ParkerPublished ยท 7 min read
A number input field with up and down spinner arrows above a ruler marking a minimum and maximum range
Key takeaways The short version, before the details.
Key pointsWhat to know before you ship a number field
  • The default step is 1, so decimals fail validation until you change it.
  • min and max are checked on submit. They do not stop anyone typing a value outside the range.
  • Text the browser cannot parse reaches your script as an empty string.
  • ZIP codes, phone numbers and card numbers belong in a text field with a numeric keypad.

HTML number input basics and what the browser adds

Here is a complete number field with a label and a range:

<label for="guests">Number of guests</label>
<input type="number" id="guests" name="guests" min="0" max="10" step="1">

With that one attribute the browser gives you four things:

  • Spinner arrows on desktop, and a numeric keyboard on most phones.
  • Up and down arrow keys that raise or lower the value by one step.
  • Built-in validation against min, max and step when the form is submitted.
  • A valueAsNumber property in JavaScript, next to the usual value.

One detail trips up a lot of code: value is always a string. Adding two of them joins the text, so "2" plus "3" gives "23". Read valueAsNumber instead, and expect NaN when the field is empty.

const input = document.querySelector('#guests');

input.value;          // "3"  (a string)
input.valueAsNumber;  // 3    (a number, or NaN when empty)

How do min, max and step work on input type number?

The three attributes describe which values count as valid. None of them filters what a person can type.

AttributeWhat it doesDefault
minLowest valid value. Also the starting point for the step grid.No lower limit
maxHighest valid value.No upper limit
stepThe gap between valid values, and how far each arrow click moves.1

The spinner arrows stop at min and max. The keyboard does not. A reader can type 500 into a field with max="10", and the browser only objects when they press submit.

Why step decides which values are valid

Valid values are counted from min in jumps of step. That makes the pair more precise than it looks:

<!-- Valid: 1, 3, 5, 7, 9. Typing 4 fails validation. -->
<input type="number" min="1" max="9" step="2">

<!-- Valid: 0, 5, 10, 15 ... -->
<input type="number" min="0" step="5">

If a field rejects a value that looks fine, check the step grid first. An odd min with an even step is the usual cause.

Input type number decimal values and which step to use

Because the default step is 1, a plain number input treats 1.5 as invalid. The reader sees a message asking for the nearest whole numbers, which is confusing on a price or weight field.

Pick the step that matches the precision you want:

<!-- Money: two decimal places -->
<input type="number" name="price" min="0" step="0.01">

<!-- One decimal place -->
<input type="number" name="weight" min="0" step="0.1">

<!-- Any decimal, no step check at all -->
<input type="number" name="reading" step="any">

Two things to know about decimals:

  • The submitted value always uses a dot. A browser set to a language that writes decimals with a comma may show and accept a comma, but value and the submitted data use a dot.
  • The phone keyboard may lack a decimal key. Adding inputmode="decimal" asks for a keypad that includes one.

What breaks with a type number input

The field does a narrow job: it accepts what the HTML standard calls a valid floating-point number. Most surprises come from that definition.

Why the letter e and minus signs get through

Scientific notation is a valid number, so 1e3 is accepted and means 1000. That is why Chrome lets you type e, +, - and . while blocking other letters.

Firefox and Safari have gone the other way and let you type any text, then mark the field invalid. Either way, you cannot rely on the field to hold only digits. Add min="0" if negative values make no sense.

Why invalid text submits as an empty value

When the text in the field is not a valid number, the browser does not hand it to your script. It reports an empty string.

What the reader types

  • Letters after digits12abc
  • Unfinished exponent1e
  • Double sign--5
  • A wordten

What input.value returns

""

An empty string every time. Only validity.badInput tells you the field is not really empty.

With normal validation the browser blocks the submit and shows a message. If the form has novalidate, or your script reads value and posts it itself, the bad entry looks exactly like a blank field. Check input.validity.badInput before treating it as empty.

Why the scroll wheel and arrow keys change the value

The up and down arrow keys step the value. In Chromium browsers, turning the mouse wheel over a focused number field does the same. People scrolling a long form can change a quantity without noticing.

If that risk matters, drop focus when the wheel moves over the field:

document.querySelectorAll('input[type="number"]').forEach((input) => {
  input.addEventListener('wheel', () => input.blur(), { passive: true });
});

Why maxlength and pattern are ignored

maxlength, minlength, pattern and size do not apply to type="number". To cap the size of a number, use max. To require an exact count of digits, you need a text field, covered in the next section and in our guide to the input pattern attribute.

HTML input only numbers: when should you use a text field?

Ask one question: would you ever add one to this value? A quantity, yes. A postcode, no.

Values made of digits that are not amounts lose information in a number field. Leading zeros disappear as soon as the value is converted to a number, the spinner arrows make no sense, and a stray scroll changes one digit of an account number.

For those, use a text input that asks for the numeric keypad and checks the digits with a pattern:

<label for="code">Verification code</label>
<input type="text" id="code" name="code"
       inputmode="numeric" pattern="[0-9]{6}" maxlength="6"
       autocomplete="one-time-code">
FieldUse
Quantity, guests, agetype="number" with min and max
Price, weight, measurementtype="number" with a decimal step
ZIP or postal code, verification codetype="text" with inputmode="numeric" and a pattern
Card or account numbertype="text" with inputmode="numeric"
Phone numbertype="tel"

The GOV.UK Design System team made the same switch for its number fields and wrote up the reasons. For phone fields and the keypad options in detail, see our post on input type tel.

How to hide arrows on input type number

The spinner is browser chrome, so you remove it with vendor selectors. This covers Chrome, Edge, Safari and Firefox:

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

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

Hiding the arrows changes the look only

The arrow keys still step the value, and the scroll wheel still can. If you are hiding the arrows because the field holds a code or an ID, a text field with inputmode="numeric" is the better fix.

Why you still need to check numbers on the server

Everything above runs in the browser, and the browser is under the visitor's control. A form posted by a script, or by a page with novalidate, skips min, max and step completely. The value also arrives as text, like every other form field.

So the server has to repeat the work: parse the text as a number, reject anything that does not parse, then compare it with the same range. Our overview of HTML form validation explains how the browser and server checks fit together.

If you build the form with SendForm, the Number question in the online form builder handles both sides. You set an optional minimum and maximum. The hosted form renders a number input with step="any", so decimals are accepted, and the same range is checked again on the server when the answer arrives. The form's report then shows the average, lowest and highest answer for that question.

For the full rules, the MDN reference for input type number and the HTML standard are the sources to trust.

SendForm online forms

Number questions with the range checked twice

Add a Number question, set a minimum and maximum, and share the form with one link. The range is checked in the browser and again on the server.

Build a form
A hosted form with a number question and its minimum and maximum settings

Frequently asked questions

Because scientific notation is a valid number. 1e3 means 1000, so browsers allow e along with plus, minus and the decimal point. If you need digits only, use a text input with inputmode="numeric" and a pattern of [0-9]*.

Set the step attribute. Use step="0.01" for two decimal places, step="0.1" for one, or step="any" to accept any decimal. Without it the default step of 1 makes every decimal value invalid.

No. maxlength, minlength, pattern and size are ignored on number inputs. Limit the value with max, or switch to a text input when you need an exact number of digits.

Yes. The max attribute stops the spinner arrows and fails validation on submit, but it does not block typing. A form sent without browser validation can carry any value, so check the range on the server too.

No. They are identifiers, not amounts. Leading zeros are lost once the value is converted to a number, and the spinner arrows make no sense. Use type="text" with inputmode="numeric" and a pattern instead, and keep phone numbers in type="tel".