Validation
A declared type is a constraint. HTML Forms extends constraint validation to any element with a value; this chapter defines how a component's prop types and a <data> source's schema produce validity in that model, so schema validation stops being a library and becomes native.
At a glance
The same validation that already works on a form <input> works on any typed value, a component prop or a data response, with no forms library:
<!-- a form control validates against its constraints, exactly as today -->
<input bind:value="email" type="email" required>
<!-- a component definition declares the bounds on its own prop -->
<template component="x-age-field">
<defs><prop name="age" type="integer" min="0" max="120">Age.</prop></defs>
<div from:data-age="age"></div>
</template>
<x-age-field from:age="draft.age"></x-age-field>
<!-- structured data validates against a schema; failures carry a path -->
<data name="profile" src="/api/me" schema="/schemas/profile.json">Built on HTML Forms
This chapter does not define a validity model of its own. HTML Forms Level 1 defines one for any element with a value:
el.validitywith named flags such asvalueMissing,rangeOverflow, andtooShort, plus a list of errors with messages and optional paths;el.validate()andel.setValidity(errors);- the
invalidevent,validationMessage, and:valid,:invalid, and:user-invalid.
That model leaves open where an element's constraints come from. A native <input> takes them from its attributes. A component takes them from its declared types, which is what this chapter defines.
The html-next implementation repository includes a pure validate(value, constraint) helper and a generalized validity surface. Authored prop constraints use the same named failures as HTML's ValidityState.2 Authors use the proposed surface; the implementation handles browser compatibility.
The type is the constraint
A prop's declared type and its constraint attributes (see Types) compile to validity. Each kind of failure produces one of HTML Forms' reasons:
| Declared | A value fails when | Reason |
|---|---|---|
required | it is empty | valueMissing |
the type (number, color, etc.) | it is the wrong kind of value | typeMismatch |
values | it is outside the permitted set | typeMismatch |
min / max | it is below or above the bound | rangeUnderflow / rangeOverflow |
minlength / maxlength | it is too short or too long | tooShort / tooLong |
pattern | it does not match | patternMismatch |
| the type's parser | it cannot be parsed as the type | badInput or typeMismatch |
| a JSON Schema rule | a rule with no reason above fails | the schema keyword, with the failing value's path |
Invalid declarations and supplied values have different effects:
- Definition: A
defaultmust satisfy its type and constraints when the definition is compiled. Build tools reject a malformed constraint; the live parser warns and ignores that constraint. - Value outside a constraint: A supplied number above
maxis still a number. It becomes the prop's current value and setsrangeOverflow. Ordinary constraint failures do not produce console warnings, whether the value is literal or comes from a reactive binding. - Value that cannot be parsed: If a number prop is supplied as
amount="oops", that string remains itsinputValueand reportsbadInputortypeMismatch. It does not become the prop's accepted value. The accepted value stays at the last successfully parsed value, or the declared default, ornullif neither exists. Template expressions andhost.props.amount.valueread that accepted value. A user's edit in a native control remains in that control's editing surface, where its validity reports the failure.
The controller can inspect both sides through the prop handle: host.props.amount.inputValue reads the latest direct input, host.props.amount.value reads the accepted value, and host.props.amount.validity describes that input. Declared props are absent from host.state. The component mounts even when the initial input cannot be parsed. Rendering follows the accepted value; the invalid input is never implicitly substituted into a template. A well-typed value outside min, max, values, or another value constraint is accepted and makes the prop invalid.
Direct supply to a number prop with default="5" | inputValue | value read by templates | validity |
|---|---|---|---|
Initial amount="oops" | "oops" | 5 | badInput |
Supply 2 | 2 | 2 | valid |
Supply "oops" | "oops" | 2 | badInput |
Supply 7 | 7 | 7 | valid |
A binding that evaluates to the wrong type does not supply the destination prop at all: its inputValue, accepted value, and validity remain unchanged. See Invalid reactive results.
Validity for a directly supplied prop describes its current input, even when conversion leaves the accepted value unchanged. Validity at a binding destination does not describe an attempt that the binding skipped. For example, if a number prop holds 2 and a binding next evaluates to the string "oops", the destination stays at 2 and remains valid; a directly supplied "oops" would keep 2 but report badInput. A statically provable mismatch in an authored expression is a build error; a live implementation may warn about a malformed authored expression, but ordinary invalid data must not generate repeated console warnings.
On a component with a non-native root, el.validity exposes the corresponding ValidityState flags and el.validity.errors gives each failure's prop path. checkValidity() reports whether the current values pass. When a component renders a native form control, the control retains its native ValidityState; the compatibility layer combines additional component failures with native validity. The Types chapter defines which constraints apply to each type.
This is the mechanism behind “the contract is the schema” (see Types). A typed prop, a bind: input, or a <data> value that fails its declared type produces a native validity error, one that form controls, components, and data sources all share.
Where the validity lives
- A typed prop gives validity to the component's root element. When that root is a native control, such as a component that lowers to
<input>, the prop's failures join the control's own, and the control still takes part in its form natively. - A
<data>source with aschemahas validity for its current value. Each failure carries thepathof the part that failed, so a form can point each error at its field. - The pure helper
validate(value, type | schema)validates raw data attached to no element and returns the same result.
el.validity // { valid: false, rangeOverflow: true, errors: [{ reason: "rangeOverflow", path: "age", message: "…" }] }
// a failure the type cannot know about, such as a server's answer
el.setValidity([{ reason: "taken", message: "That email is in use." }])
// a pure helper: validate any value against a type or schema, no element involved
validate(value, type | schema) // → { valid, errors }When validation runs
A component's values are already reactive dependencies, so validity recomputes whenever a value changes; el.validity is always current, and nothing needs to trigger it. el.validate() is for an explicit check, such as at submit time. Showing an error follows HTML Forms: it waits for interaction, through :user-invalid.
For an asynchronous rule, such as whether a name is taken, a <data> lookup keeps the answer current and setValidity() applies it (see Reactivity).
How it runs today
Authors set validity through setValidity() and style :valid, :invalid, and :user-invalid. They do not target polyfill attributes. Until browsers implement HTML Forms' validity for every element, the reference implementation provides it:
- native form controls use their real
setCustomValidity(), so the real:invalidand form submission still work; - form-associated custom elements use the real
ElementInternals.setValidity(); - every other element gets the same validity object and event, and the matching ARIA state.
When browsers expose validity on every element, the implementation steps aside without any change to component source or application CSS.
References
- HTML Forms Level 1, constraint validation on any element (the validity model this chapter builds on).
- WHATWG HTML, the Constraint Validation API and ValidityState.
- CSS Selectors Level 4, validity pseudo-classes (
:valid,:invalid,:user-invalid). - IETF JSON Schema; Zod / Valibot (the issue-list model this mirrors).