errno

A phone input mask that silently rewrote the number I typed

· tested on jquery.mask.min.js, input with minlength=maxlength=14, national phone format

Numbers in this post are placeholders; the original test used a real number that has been redacted.

What happened

A registration form had a mobile number field with a national format mask:

<input type="text" class="form-control mask-phone" name="phone"
       maxlength="14" minlength="14" required placeholder="Mobile number">

I filled it with a foreign number in international form. The field did not go red, no message appeared — the value became:

input:  +421900123456
field:  06

Typing digit by digit was worse. The mask pre-seeds the national trunk prefix, so my leading digits were consumed as part of it and the rest shifted:

typed:  06301234567        ->  field: 06 06 012-3456
typed:  301234567          ->  field: 06 12 345-67

That third case is the dangerous one. 06 06 012-3456 is fourteen characters, matches the mask, passes checkValidity() — and is a different phone number than the one I entered. The submit button enabled itself and the form was ready to send someone else’s number.

Why it is worse than a rejection

A mask is a formatter. Given input it cannot represent, it does not fail — it produces the closest thing it can. Combined with minlength = maxlength the result passes every client-side check, so:

Setting the value programmatically hits the same wall — the mask handler rewrites it on input:

el.value = '+421900123456';
el.dispatchEvent(new Event('input', { bubbles: true }));
el.value;   // "06 "

The only value that survived was one already shaped like the mask (06 30 123-4567), which is exactly the tell: the widget accepts its own output and mangles everything else.

How to detect it in your own forms

Type a value that cannot fit the mask, then compare what you typed with what the field holds:

const el = document.querySelector('[name=phone]');
el.focus(); el.value = '';
for (const ch of '+421900123456') {
  el.value += ch;
  el.dispatchEvent(new Event('input', { bubbles: true }));
}
console.log({ typed: '+421900123456', stored: el.value, valid: el.checkValidity() });

If stored differs from typed and valid is true, you have this bug. The pass condition is not “the field is valid” — it is “the field contains what the user meant, or it is visibly invalid”.

What to do instead

The broader point: a validator that repairs input instead of refusing it moves the error from the person who can fix it to a database row nobody will look at again.

forms validation javascript ux