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,maxandstepwhen the form is submitted. - A
valueAsNumberproperty in JavaScript, next to the usualvalue.
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.
| Attribute | What it does | Default |
|---|---|---|
min | Lowest valid value. Also the starting point for the step grid. | No lower limit |
max | Highest valid value. | No upper limit |
step | The 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
valueand 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">| Field | Use |
|---|---|
| Quantity, guests, age | type="number" with min and max |
| Price, weight, measurement | type="number" with a decimal step |
| ZIP or postal code, verification code | type="text" with inputmode="numeric" and a pattern |
| Card or account number | type="text" with inputmode="numeric" |
| Phone number | type="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.
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".
