A label, a control, a description and an error wired together, with native validation that never moves the layout.
Example
import {
Field, FieldLabel, FieldDescription, FieldError, FieldContent, FieldTitle, FieldSet, FieldLegend,
FieldGroup, FieldSeparator,
} from "@elements/ui/field";
import { Input } from "@elements/ui/input";
import { Checkbox } from "@elements/ui/checkbox";
<Field label="Email" description="We never share it." required>
<Input type="email" value={form.email} />
</Field>
<Field label="Username" validate={checkUsername} validationMode="onBlur">
<Input value={form.username} attrs={{ minlength: 3 }} />
<div slot="error">
<FieldError match="tooShort">At least 3 characters.</FieldError>
</div>
</Field>
<Field orientation="horizontal" label="Email me updates">
<Checkbox checked={form.updates} />
</Field>
Parts
| Part |
What it is |
FieldLabel |
<label class="label field-label">. Wired to the control by the field. Wrapping a Field, it makes a choice card. |
FieldDescription |
<p class="hint field-description">, added to aria-describedby. |
FieldError |
<div class="error field-error-part">: a message shown by match. |
FieldContent |
A column for a label and description beside a control. |
FieldTitle |
Label-styled text that is not a <label>, for choice cards. |
FieldSet / FieldLegend |
A native fieldset and legend with the box removed. |
FieldGroup |
A column of fields, and the container a responsive field measures. |
FieldSeparator |
A rule between groups, with optional text. |
.field-message |
The reserved line: the description and the error share it. |
Attributes
Field
| Attribute |
Type |
Default |
Description |
label |
string |
"" |
The label, wired to the control. Or use a FieldLabel part. |
description |
string |
"" |
Help text under the control, wired with aria-describedby. Or use FieldDescription. |
error |
string |
"" |
A message from elsewhere (the server). Shows at once and makes the field invalid. |
errors |
string[] |
[] |
Several messages from elsewhere; render as a list. |
invalid |
boolean |
false |
Force the invalid state with no message. |
disabled |
boolean |
false |
Disables every control inside and dims the field. |
required |
boolean |
false |
Required on the control, and a marker on the label. |
name |
string |
"" |
Form name for the control. |
orientation |
"vertical" | "horizontal" | "responsive" |
"vertical" |
Label above; control first with the label beside it; or a column that becomes a row in a wide FieldGroup. |
validationMode |
"onSubmit" | "onBlur" | "onChange" |
"onSubmit" |
When errors first show. After that they follow every change. |
validationDebounceTime |
number |
0 |
onChange: wait this many ms after typing. |
validate |
(value: string, control: HTMLElement, formValues: FormValues) => string | string[] | null | Promise<string | string[] | null> |
() => null |
Your rule. Return a message (or several) when invalid, or a Promise of them for a server check. It blocks a native submit (a pending Promise does not). |
reserveError |
boolean |
true |
Keep the message line even with no description, so an error never moves anything. |
dirty |
boolean |
undefined |
Controls the dirty state when another library owns it; unset, the field tracks it. |
touched |
boolean |
undefined |
Controls the touched state when another library owns it; unset, the field tracks it. |
class |
string |
"" |
Extra classes on the field. |
onValidityChange |
(state: { valid, errors, error, value, initialValue, validity }) => void |
() => {} |
After every validation and a form reset, with the ValidityState flags. |
FieldLabel
| Attribute |
Type |
Default |
Description |
required |
boolean |
false |
Draws the required marker. |
attrs |
Record<string, string | number | boolean | undefined> |
{} |
Attributes for the <label>. |
FieldError
| Attribute |
Type |
Default |
Description |
match |
ValidityKey | "always" | "" |
"" |
Show when this ValidityState flag is set (valueMissing, typeMismatch, tooShort...). Empty shows whenever the field is invalid; valid shows when it passes. |
FieldSet
| Attribute |
Type |
Default |
Description |
disabled |
boolean |
false |
Native fieldset disabled: disables every control inside. |
FieldLegend
| Attribute |
Type |
Default |
Description |
variant |
"legend" | "label" |
"legend" |
label sizes the legend like a field label, for nested sets. |
Slots
| Slot |
Part |
Content |
default |
Field |
The control, and any parts: FieldLabel, FieldDescription, FieldContent. |
error |
Field |
FieldError parts, in one wrapper element: they share the reserved message line. |
default |
FieldSet |
Fields, a FieldLegend and a FieldDescription. |
default |
FieldSeparator |
Optional text in the middle of the rule. |
Keyboard
| Keys |
Action |
Click on the label |
Focuses or toggles the control. |
Enter (in a form) |
Submits; an invalid field shows its error and takes focus instead. |
Data attributes
| Attribute |
Values |
On |
Description |
data-orientation |
"vertical" | "horizontal" | "responsive" |
field |
|
data-invalid |
present while an error shows |
field |
|
data-valid |
present once validated and passing |
field |
|
data-disabled |
present while disabled |
field |
|
data-required |
present when required |
field |
|
data-dirty |
present when the value differs from the first one |
field |
|
data-touched |
present after focus has left once |
field |
|
data-filled |
present while the control has a value |
field |
|
data-focused |
present while focus is inside |
field |
|
data-error-shown |
present while an error message is on screen |
field |
|
data-visible |
present while this error is shown |
FieldError |
|
data-field-control |
present |
a custom control |
Set by a template that is not a native control on its focusable element: the label, description and aria-invalid go there. |
data-field-input |
present |
a custom control's value input |
An input beside the control that carries its form value (Select). Field validates it, names it and marks it required. |
data-custom-validity |
the message |
a control's input |
The control's own custom validity (NumberInput's step check). Field keeps it and shows it as an error. |
Accessibility
- The field finds its control and wires it: label for (or aria-labelledby for a radio group), aria-describedby to the description and the shown error, aria-invalid while invalid.
- The field is role="group" named by its label; FieldSet and FieldLegend group related fields natively.
- Validation is the browser's constraint validation plus validate() through setCustomValidity, so an invalid form does not submit and the first invalid control takes focus.
- Errors are in an aria-live region and take the description's line, so nothing on the page moves when one appears.
See also