DEV Community

Vladimir Elchinov for Session Replay

Posted on

Your Number Input Reads Empty, and the User Did Type Something

The support ticket is short and sounds impossible. The form says the price is required. There is a price in the box. They can see it, they photographed it, and your validation still refuses to move on.

Both halves are true. The box contains 1,5 and your code is reading the empty string.

The rule that erases it

The HTML Standard defines a value sanitization algorithm for <input type="number">, and it is one sentence:

If the value of the element is not a valid floating-point number, then set it to the empty string instead.

Not "leave it alone". Not "keep the text and let the author decide". Set it to the empty string.

And a valid floating-point number is defined precisely, as optional sign, digits, and:

A single U+002E FULL STOP character (.)

A full stop. The grammar has no comma in it anywhere, in any locale, under any language setting. Roughly half the planet writes decimals with a comma, and this is one of the places where that is not a formatting preference but an invalid character.

The part that actually costs you

Here is the bit worth the ten minutes, and it is not the comma.

When a value fails that check, input.value is "". The same "" you get from a field nobody has touched. The two situations are now indistinguishable to every piece of code you own:

if (!priceInput.value) {
  // "You must enter a price."
  // Was the field blank, or did we just throw away what they typed?
}
Enter fullscreen mode Exit fullscreen mode

Your required-field check cannot tell them apart. Neither can your form analytics, which will record an abandoned field. Neither can the log line you add while debugging it, because you will log value and see an empty string and believe the user.

That is why this report arrives as a contradiction. The user says there is a number in the field. Your instrumentation says the field is empty. Nobody is lying and nobody can see the discrepancy from where they are standing.

The one field that knows

The browser does keep the distinction. It is just somewhere nobody looks:

if (priceInput.validity.badInput) {
  // They typed something. It could not be converted.
} else if (!priceInput.value) {
  // Genuinely empty.
}
Enter fullscreen mode Exit fullscreen mode

MDN defines badInput as indicating "if the user has provided input that the browser is unable to convert", and gives this exact case as its example: "if you have a number input element whose content is a string".

Two branches instead of one, and the error message can stop being a lie. "Use a full stop for decimals" is a message somebody can act on. "This field is required" is a message that makes them doubt their own eyes.

Worth knowing: the sibling behaves differently

type="range" has the same kind of sanitization rule and a different ending. Where number sets the value to the empty string, range sets it to a default instead.

So the same class of mistake is loud in one control and silent in the other. On a range, bad input quietly becomes a legitimate-looking number that the user never chose, and nothing anywhere reports a problem. Worth remembering if you are tempted to treat these two as variations on one input.

Why you will never see it

This failure is selected by the user's keyboard and locale rather than by their browser, their operating system or their network. It has no version number attached to it.

If your machine and your test data produce a full stop, the field works perfectly for you forever. There is no flag to toggle, no device lab that surfaces it, and no field in any bug report template that asks which decimal separator somebody's culture uses. The closest you will get is noticing that the complaints cluster in one country, which takes a surprisingly long time when each one arrives individually and sounds like user error.

The whole thing is recoverable from one boolean the browser was willing to tell you all along. It just never came up, because the value looked empty and empty looked like an answer.

Top comments (0)