Form Field
A presentational layout wrapper — label row, control slot, and hint/error text. Pure .astro with no runtime JS; drop any control into the slot. Error text replaces help text when present.
Preview
We will never share your email.
<FormField label="Email" helpText="We will never share your email.">
<input type="email" aria-label="Email" placeholder="you@example.com" />
</FormField> States
Mark a field required, surface an errorText in place of help text, or disabled the whole field.
The control is yours to style — this wrapper only owns the label, help and error
text. The plain <input> below is demo markup: its border comes
from a class in this page's own stylesheet, not from the design system. What the wrapper
does give a slotted control is the focus ring — an
errorText re-points --focus-ring-color to the error red, and
because custom properties inherit, that reaches a native control and an
esa-* component in a shadow root alike. Tab into Permit ID to see it.
For a real form, reach for Text Field
instead: it owns its label, help text, error text and border states, so you are not
hand-rolling three of the four.
Permit ID is required.
This field cannot be edited.
<FormField label="Project name" required>
<input type="text" aria-label="Project name" />
</FormField>
<FormField label="Permit ID" errorText="Permit ID is required." required>
<input type="text" aria-label="Permit ID" aria-invalid="true" />
</FormField>
<FormField label="Locked field" helpText="This field cannot be edited." disabled>
<input type="text" aria-label="Locked field" disabled value="Read only" />
</FormField> Sizes
The size prop scales the label; size the control yourself to match.
<FormField label="Extra small label" size="xs"> … </FormField>
<FormField label="Small label" size="sm"> … </FormField>
<FormField label="Medium label" size="md"> … </FormField>
<FormField label="Large label" size="lg"> … </FormField> API
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | '' | Label rendered above the control slot. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'md' | Scales the label font size. |
helpText | string | '' | Helper text shown below the control. |
errorText | string | '' | Error message; replaces help text when present. |
required | boolean | false | Renders an asterisk on the label. |
disabled | boolean | false | Dims the field and blocks pointer events. |
The control goes in the default slot — the wrapper makes no assumptions about it.
Accessibility
- The wrapper is presentational only. Its
<label>has noforand does not wrap the slot, so it names nothing on its own — associate it yourself with afor/idpair. - That is only possible for a native control. The
esa-*Lit controls render into shadow DOM, and a light-DOMforcan never reach an input inside a shadow root. Around one of those, treat this label as a visual caption and set the control's ownlabelattribute instead — that one is associated. helpTextanderrorTextrender without anid, so they cannot be referenced byaria-describedbyeither, anderrorTextis not in a live region. Prefer the control's ownhelp-text/error-textwhere it has them.- The required asterisk is
aria-hidden, because it is decoration: it used to carryaria-label="required", which does nothing at all —aria-labelis ignored on a bare<span>(role=generic). The requirement itself must be set on the control, asaria-requiredor the nativerequiredattribute. Everyesa-*form control now does both when givenrequired. - Set
aria-invalid="true"on the slotted control whenevererrorTextis shown. disabledisopacity+pointer-events: none— it blocks the mouse only. The slotted control stays keyboard-focusable, operable, and still submits, so you must also disable it natively.- The wrapper adds no grouping semantics. Around a set of radios or checkboxes it renders a bare
<div>; the group needs its own name (see the forms contract in theaccessibilityskill).