Why is <input type="number"> a poor choice for phone numbers, postal codes and account IDs, and what markup would you use instead?
answer
- quantity versus digit string
- would adding one to it mean anything?
- unparseable input reads back as empty
- spinners, arrow keys, the scroll wheel
- the keypad without the number semantics
basics
~20 stype="number" holds a quantity, not a digit string: anything it cannot parse as a number is read back as an empty string, and spinners, arrow keys and the scroll wheel can change it. Use type="text" with inputmode="numeric", or type="tel" for phones.
solid answer
~50 s`type="number"` means "a number you could do arithmetic on", and the browser enforces that. Its value-sanitising step throws away anything that is not a valid floating-point number, so a pasted `+1 (555) 010-1234` comes back from `value` as an empty string — the user sees text in the box and your code sees nothing. On top of that it renders spin buttons, increments on arrow keys and, in some browsers, on the mouse wheel over a focused field, and it maps to a spinbutton role, so assistive tech announces it as something you nudge up and down. None of that fits an identifier: phone numbers, postcodes and card numbers are digit *strings* where `+`, spaces and leading zeros carry meaning and arithmetic is nonsense. The right markup is `type="text"` plus `inputmode="numeric"` — a plain text field that still summons a numeric keypad on phones — or `type="tel"` for telephone numbers. Keep `type="number"` for quantities, prices and ages.
code
html · 11 lines<!-- Wrong: an identifier modelled as a quantity -->
<label for="acct-bad">Account number</label>
<input id="acct-bad" name="acct" type="number">
<!-- Right: a digit string with a numeric keypad hint -->
<label for="acct">Account number</label>
<input id="acct" name="acct" type="text" inputmode="numeric" autocomplete="off">
<!-- Right: a genuine quantity keeps type=number -->
<label for="qty">Quantity</label>
<input id="qty" name="qty" type="number" min="1" step="1" value="1">go deeper
Remember the rule of thumb: if adding one to the value is meaningless, it is not a number field. Reach for type="text" with inputmode="numeric", or type="tel" for phones.
Explain the mechanics — the value-sanitising step that empties an unparseable value, the spinbutton role and stepping keys, and the default step of 1 that rejects decimals.
Show you have debugged this in production: a silently empty value with no error, leading zeros lost through a numeric round trip, card digits mangled by float precision, wheel-scroll edits on a focused field.
Own the convention across the codebase — a shared numeric-string input so teams stop rediscovering this, and a rule that identifiers stay strings through the API and schema, not only in the markup.
## The distinction the question is really testing There are two different things people call "a number": a **quantity** you can add, compare and step through, and a **digit string** that happens to be written with digits. A price, an age, a quantity in a basket are quantities. A phone number, a postcode, an order reference, a card number, a national ID are digit strings. `<input type="number">` implements the first meaning only, and every complaint about it follows from that one fact. ## What the number state actually does **It sanitises the value.** The number state defines a value-sanitisation step: if what the control holds is not a valid floating-point number, the value becomes the empty string. This is the sharp edge. The user pastes `+1 (555) 010-1234`, the browser may keep showing something in the box, and your script reads `value` and gets `""`. There is no error, no exception, nothing to log — the data simply is not there. The same happens with `1 234`, `12-34`, and a trailing comma from a spreadsheet paste. **It renders a spinner and binds keys.** Up and down arrows increment and decrement. Most desktop browsers draw spin buttons. Several browsers change the value when the wheel is scrolled over a focused number field, which is a real source of "I scrolled the page and my quantity changed" bug reports. **It changes the accessible semantics.** A number input maps to the `spinbutton` role rather than `textbox`. Screen reader users are told this is a value they step through, and get spinbutton interaction affordances — appropriate for a quantity, misleading for an account number. **It constrains the grammar.** No `+` at the start followed by formatting characters, no spaces, no parentheses, no hyphens — all of which are ordinary in written phone numbers. And because the underlying value is a number, exponent forms such as `1e5` are legal input, which almost nobody wants in an ID field. ## The leading-zero and precision traps Even where the value survives, the *meaning* may not. `00042` and `42` are the same quantity but different identifiers. Anything that round-trips the value through a numeric type — your own script, a form library, a backend that types the column as an integer — silently loses the padding. And very long digit strings such as a 19-digit card number exceed the exact-integer range of a double-precision float, so any numeric conversion corrupts the last digits. A digit string must stay a string end to end. ## The correct markup ```html <!-- a quantity: number is right --> <label for="qty">Quantity</label> <input id="qty" type="number" name="qty" min="1" value="1"> <!-- a digit string: text plus a keyboard hint --> <label for="zip">Postal code</label> <input id="zip" type="text" name="zip" inputmode="numeric"> <!-- a phone number --> <label for="tel">Phone</label> <input id="tel" type="tel" name="tel"> ``` `type="text"` keeps the value exactly as typed, keeps the textbox role, and removes the spinner. `inputmode="numeric"` is a pure keyboard hint: on a touch device the user still gets a numeric keypad, which was the only genuine reason most teams reached for `type="number"` in the first place. `type="tel"` is the dedicated state for telephone numbers — it applies no format validation at all, because phone formats vary worldwide, but it summons a telephone keypad and tells autofill and assistive tech what the field is for. ## When number is the right answer Do not over-correct. If the field is a quantity — a count, an amount, a temperature, a rating, an age — `type="number"` is exactly right and gives you stepping, a numeric keyboard, and a control that says "this is a magnitude". `step` and the min/max range make sense there because arithmetic makes sense there. For money, be aware the default step is 1, so a bare number input rejects `19.99` as invalid until you set a decimal step. ## The one-line rule Ask: would it ever make sense to add one to this value? If yes, `type="number"`. If adding one is meaningless — a phone number, a postcode, an ID, a card — it is a string, and the markup is `type="text"` with `inputmode="numeric"`, or `type="tel"`.
- If you move to type="text", how do mobile users still get a numeric keypad?With `inputmode="numeric"`, which is a hint to the virtual keyboard and nothing else — it changes no semantics and applies no validation, so the value stays an untouched string. Use `inputmode="decimal"` when a separator is legitimate, and `type="tel"` for phone numbers, which brings the telephone keypad plus a field purpose the browser and assistive tech understand.
- Why does a bare <input type="number"> refuse a price like 19.99?The number state's step defaults to 1, so values are only allowed on whole-number increments from the base and 19.99 is off the step. Setting `step="0.01"` allows two decimals — or `step="any"` removes the constraint entirely. It is a common gotcha because the field looks like it should accept any number.
- Is there any harm in leaving a card number as type="number" if users only ever type digits?Yes. A 16-to-19 digit card number exceeds the exact-integer range of a double, so anything that converts the value numerically corrupts the trailing digits, and leading zeros in other identifiers vanish the same way. You also inherit spin buttons and the spinbutton role on a field nobody should be stepping. Keep it a string end to end.
- Users report a quantity field changing on its own while they scroll the page. What is happening?The number input has focus and the mouse wheel is being interpreted as a step, so scrolling over it increments or decrements the value. It is browser behaviour that comes with the number state; teams typically blur the field or suppress wheel-driven stepping. It is a good illustration that choosing number means opting into stepping interactions, not just a numeric keyboard.
saying these in an interview costs you the question
- Uses type=number for a phone number to get a numeric keypad
- Thinks unparseable input is preserved verbatim in value
- Believes leading zeros survive a numeric round trip
- Assumes type=number accepts decimals without setting step
- Says type=tel validates the phone number format