AdiaUI's form system uses UIFormElement and ElementInternals to give custom elements native form participation, constraint validation, and auto-validation UX.
HTML forms expect native elements (<input>, <select>)
to participate in submission, validation, and reset. Custom elements are invisible to
forms by default. AdiaUI solves this with UIFormElement, a base class
that bridges custom elements into the native form lifecycle via the
ElementInternals API.
Any component that extends UIFormElement can be placed inside a
<form> and will:
name attribute)
The following components extend UIFormElement and participate in forms:
| Component | Tag | Value Type |
|---|---|---|
| Input | input-ui | String |
| Textarea | textarea-ui | String |
| Select | select-ui | String |
| Check | check-ui | String (on/off) |
| Switch | switch-ui | String (on/off) |
| Radio | radio-ui | String |
| Slider | slider-ui | Number (as string) |
| Range | range-ui | Number (as string) |
| Search | search-ui | String |
| Segmented | segmented-ui | String |
| OTP Input | otp-input-ui | String |
| Calendar Picker | calendar-picker-ui | Date string |
| Color Picker | color-picker-ui | Color string |
| Upload | upload-ui | File(s) |
ElementInternals is the browser API that lets custom elements act as form
controls. AdiaUI's UIFormElement sets static formAssociated = true
to opt in, then uses the internals object to:
setFormValue(val) — set the value submitted with the formsetValidity(flags, message, anchor) — set validation statecheckValidity() / reportValidity() — trigger validation.form, .labels, .validity
UIFormElement implements four constraint types that mirror native HTML
validation:
| Attribute | Validity Flag | Default Message |
|---|---|---|
required |
valueMissing |
"This field is required." |
pattern="regex" |
patternMismatch |
"Please match the requested format." |
minlength="n" |
tooShort |
"Please use at least n characters." |
maxlength="n" |
tooLong |
"Please use no more than n characters." |
AdiaUI implements a progressive validation UX that avoids showing errors prematurely. The lifecycle is:
invalid event on any
field that fails validation. UIFormElement catches this, sets
aria-invalid="true", and populates the error attribute with
the validation message.error
attribute removed, aria-invalid removed, dirty flag reset.The "dirty" flag ensures blur validation only fires after the user has actually interacted with the field. A pristine field that receives focus and then loses it will not show an error.
Override the default validation messages using data-msg-* attributes
on any form element:
| Attribute | Overrides |
|---|---|
data-msg-required |
Message when field is empty and required |
data-msg-pattern |
Message when value doesn't match pattern |
data-msg-minlength |
Message when value is shorter than minlength |
data-msg-maxlength |
Message when value exceeds maxlength |
For cases where you need to validate programmatically or set custom errors (e.g. server-side validation), use these methods:
| Method | Returns | Description |
|---|---|---|
validate() |
boolean |
Runs all constraints, surfaces error if invalid, clears if valid |
setInvalid(message) |
void |
Sets a custom error — useful for async/server validation |
setValid() |
void |
Clears all validation errors and aria-invalid |
syncValue(val) |
void |
Updates the form value and re-runs constraint validation |
UIFormElement implements the standard form-associated custom element
callbacks. Subclasses can override the hook methods:
| Browser Callback | Override Hook | When It Fires |
|---|---|---|
formResetCallback() |
onFormReset() |
When the parent form is reset |
formDisabledCallback(disabled) |
onFormDisabled(disabled) |
When a parent fieldset is disabled/enabled |
formAssociatedCallback(form) |
onFormAssociated(form) |
When the element is associated with a form |
formStateRestoreCallback(state, reason) |
onFormStateRestore(state, reason) |
When browser restores form state (back/forward navigation) |
Try submitting the form below without filling in the fields to see auto-validation in action:
To create a new form-participating component, extend UIFormElement
and call syncValue() whenever the internal value changes:
Always call syncValue() after changing the component's value. This
updates the form's submission data and runs constraint validation so errors
clear as soon as the value becomes valid.
UIFormElement, not UIElement, when the component must participate in native form submission. UIFormElement handles ElementInternals, constraint validation, and the dirty-state lifecycle automatically.syncValue() after changing the component's internal value. This updates the form's submission data and runs constraint validation so errors clear as soon as the value becomes valid.field-ui error attribute directly. Use setCustomError() and clearErrors() from the base class — these coordinate with the field wrapper's ARIA live region.name attribute for its value to be included in FormData on submit. Components without name are invisible to the form.field-ui required, not only on the inner control. The field wrapper surfaces the asterisk and the ARIA required state; the inner control handles native constraint validation.validity.valid = false from the outside. If you need a custom error from application code, use the MCP submit_feedback pattern or call setCustomValidity() on the internal form element.