An HTML form control exposes a validity property returning a ValidityState object. Which flags does it carry, and how do you use them to tell why a field is invalid rather than just that it is?
answer
- one boolean per failure reason
- not just invalid — invalid why
- the value can read empty while wrong
- no attribute behind one of the flags
- length flags wait for a user edit
basics
~20 sValidityState exposes one boolean per failure reason — valueMissing, typeMismatch, patternMismatch, tooShort, tooLong, rangeUnderflow, rangeOverflow, stepMismatch, badInput, customError — plus valid. Test the specific flag to choose the right message instead of inferring it from the value.
solid answer
~50 s`element.validity` is a live object with one boolean per way a control can fail, so you can branch on the reason. `valueMissing` means `required` is unsatisfied; `typeMismatch` means the value is not shaped like the control's `type`, such as `email` or `url`; `patternMismatch` means `pattern` failed; `tooShort` and `tooLong` map to `minlength` and `maxlength`; `rangeUnderflow`, `rangeOverflow` and `stepMismatch` map to `min`, `max` and `step`; `customError` is whatever you set with `setCustomValidity()`. There is also `badInput`, which means the browser could not parse what the user typed at all — a `type="number"` field holding "12abc" is the standard case. And `valid`, which is `true` when every other flag is `false`. The reason this matters is that reading `element.value` cannot distinguish those cases: with `badInput` the value reads as the empty string, so "it looks empty" is ambiguous between a missing entry and unparseable input.
code
javascript · 12 linesconst qty = document.querySelector('#qty'); // <input type="number" min="1" required>
qty.addEventListener('invalid', () => {
const v = qty.validity;
// "12abc" makes value === '' while badInput is true:
// without the flags this is indistinguishable from an empty field.
const text = v.valueMissing ? 'Enter a quantity.'
: v.badInput ? 'Digits only, please.'
: v.rangeUnderflow ? `Minimum is ${qty.min}.`
: qty.validationMessage;
console.log(text, qty.value === '');
});go deeper
Know that element.validity is an object of booleans, not a single boolean, and that valueMissing corresponds to required while patternMismatch corresponds to pattern.
Map each flag to the attribute behind it, and explain badInput as the one with no attribute — the state where value reads empty even though the user typed something.
Use the flags as a data model: branch on them for copy in the page's own language, and instrument which flag fires per field to find constraints that are fighting real users.
Decide where error copy lives so wording stays consistent across a product, and treat a high failure rate on one flag as a signal that the constraint itself is mis-specified rather than that users are careless.
## The object Every form-associated control has a read-only `validity` property returning a `ValidityState`. It is a set of booleans, one per distinct failure mode, plus a summary flag. Reading it is how you turn "invalid" into "invalid *because*", which is what a decent error message needs. | Flag | Set when | |---|---| | `valueMissing` | `required` is present and the control has no value | | `typeMismatch` | the value does not match the syntax implied by `type` (`email`, `url`) | | `patternMismatch` | `pattern` is present and the value does not match it | | `tooShort` | the value is shorter than `minlength` | | `tooLong` | the value is longer than `maxlength` | | `rangeUnderflow` | the value is below `min` | | `rangeOverflow` | the value is above `max` | | `stepMismatch` | the value does not sit on the `step` grid | | `badInput` | the browser cannot convert the user's entry into a value at all | | `customError` | `setCustomValidity()` was given a non-empty string | | `valid` | every flag above is `false` | ```js function messageFor(input) { const v = input.validity; if (v.valid) return ''; if (v.valueMissing) return 'Enter your email address.'; if (v.typeMismatch) return 'That does not look like an email address.'; if (v.tooShort) return `At least ${input.minLength} characters.`; if (v.badInput) return 'Only digits are accepted here.'; return input.validationMessage; // browser's own text as the fallback } ``` More than one flag can be `true` at once, so order your branches by what is most useful to say first. ## badInput is the interesting one `badInput` has no attribute behind it. It means the control's UI holds something the browser cannot interpret as a value of that type. Typing "12abc" into `<input type="number">` is the canonical case: the browser keeps the characters on screen but `input.value` returns the **empty string**, because there is no number to report. That is why `if (input.value === '')` is a broken substitute for the API. An empty read means either the user entered nothing (`valueMissing`) or entered nonsense (`badInput`), and those deserve entirely different messages — "this field is required" is confusing and slightly insulting when the user can plainly see their typing in the box. Only the flags tell them apart. The same pattern shows up in date controls with a partially filled entry. ## The dirty-value nuance on length `tooShort` and `tooLong` only apply once the value has been edited by the user. A value that arrives from the server in the markup, below `minlength`, does not report `tooShort` until it is touched — the specification deliberately avoids punishing a value the user never entered. `maxlength` also mostly prevents over-long typing in the first place, so `tooLong` shows up far less often than people expect; it appears mainly when a value is set programmatically or pasted in a way the control accepts. ## What the flags are good for Three things, in practice. **Better copy.** The browser's `validationMessage` is generic and in the browser's UI language rather than the page's. Branching on the flag lets you write the sentence that actually helps: "Choose a date in 2026 or later" instead of "Value must be greater than or equal to 2026-01-01". **Instrumentation.** Counting which flag fires per field on real traffic tells you which constraint is fighting your users. A `patternMismatch` rate of thirty percent on a phone field usually means the pattern, not the users, is wrong. **Correct branching in custom UI.** When you have suppressed the native bubbles, the flags plus `validationMessage` are the entire data model your error renderer works from. ## Related properties worth naming `validationMessage` is the string the browser would show (empty when valid). `willValidate` is `false` for controls that are excluded from validation altogether — `disabled`, `readonly`, `type="hidden"`, buttons, anything inside a `<datalist>` — and checking it explains the occasional "my required attribute is being ignored". ## Weak answers Saying "`validity` returns true or false" confuses the object with its `valid` flag. Saying "an empty `value` means the field is empty" misses `badInput` entirely. And reaching for a hand-rolled regular expression to re-derive the reason, when the browser already computed it, is exactly the reinvention this API exists to prevent.
- Which flag has no corresponding HTML attribute, and why does it matter?`badInput`. It means the control holds something it cannot convert into a value — "12abc" in a `type="number"` field — and in that state `value` reads as the empty string. Without checking the flag you would call it a missing value and show "this field is required" to a user staring at their own typing.
- Can more than one flag be true at the same time?Yes. A value can be both `patternMismatch` and `tooShort`, for instance. `valid` is simply the negation of all of them together, so a renderer branches in the order that produces the most useful sentence rather than assuming exactly one cause.
- Why might a required attribute appear to be ignored entirely on a control?Because the control is barred from constraint validation: `disabled`, `readonly`, `type="hidden"`, a button type, or an ancestor `<datalist>`. Its `willValidate` property is `false`, its `checkValidity()` returns `true`, and the form submits happily. Check `willValidate` before hunting for a bug elsewhere.
saying these in an interview costs you the question
- validity is just a boolean, true or false
- An empty value always means the field was left blank
- Only one validity flag can be true at a time
- tooLong fires as soon as typing exceeds maxlength
- Re-deriving the reason with your own regex is equivalent