Field
The custom element is <hui-field>.
Overview
A light-DOM wrapper that wires a rendered label, description and error to one control. It has no shadow root and no focus of its own, and it re-wires through a MutationObserver so an HTMX swap that adds an error message is picked up automatically.
Example
Source
<hui-field> <span data-label>Name</span> <hui-input data-control value="Ada"></hui-input> <span data-description>Your full name</span></hui-field><hui-field invalid> <span data-label>Code</span> <hui-input data-control value="ab" pattern="[0-9]{3}" required></hui-input> <span data-description>Three digits</span> <span data-error>Must be three digits</span></hui-field>The Go template that renders it:
<hui-field {{ if .Invalid }}invalid{{ end }}> <span data-label>{{ .Label }}</span> <hui-input data-control name="code" value="{{ .Value }}" required></hui-input> <span data-description>Three digits</span> {{ if .Error }}<span data-error>{{ .Error }}</span>{{ end }}</hui-field>API
Attributes2
| Name | Type | Default | Description |
|---|---|---|---|
invalid | boolean | false | Marks the wrapped control aria-invalid="true". Reflected. |
required | boolean | false | Marks the wrapped control aria-required="true". Reflected. |
Properties2
| Name | Type | Default | Description |
|---|---|---|---|
invalid | boolean | false | Reflects to the invalid attribute. |
required | boolean | false | Reflects to the required attribute. |
States
Required
required marks the label and passes the constraint to the control the field wraps.
Source
<hui-field required> <span data-label>Email</span> <hui-input data-control name="email" type="email" placeholder="you@example.com"></hui-input></hui-field>Server-side mechanics
hui-field owns no form value; it is a light-DOM wrapper the server renders around a control, using the data-label, data-control, data-description and data-error child hooks to wire the control's accessible name and description. The server owns the invalid and required attributes and the text of those children, and the wrapped control submits under its own name, not the field's. An hx-swap that adds or replaces an error message is picked up automatically: a MutationObserver re-wires the field on child, text and attribute changes. There is no field-level validation of its own, so invalid only forwards aria-invalid="true" to the control.
Accessibility
- A child marked
data-labelsupplies the control accessible name througharia-labelandaria-labelledby. - Children marked
data-descriptionanddata-errorare joined intoaria-description, and their generated ids becomearia-describedbyandaria-errormessage. - A
data-errorthat ishiddenor empty is not an error yet: it is not drawn, glyph included, and not wired to the control until it has a message. A host can render the slot once and fill or reveal it later. - The description text precedes the error text in the accessible description, matching the rendered order.
- Because it is light DOM, the IDREF attributes it sets resolve for both native controls and the shadow controls, whose internal inputs receive the text through ARIA forwarding.
- A MutationObserver re-wires the field on child, text or attribute changes, so a swapped-in error message is announced without extra JavaScript.
Keyboard
| Keys | Action |
|---|---|
Tab | Moves focus straight to the wrapped control; the field wrapper is never a tab stop. |
Control keys | Every key is handled by the wrapped control; hui-field adds no key handling of its own. |
Gotchas
- It is light DOM with no shadow root:
createRenderRootreturns the host, so its contract is thedata-label,data-control,data-descriptionanddata-errorchild hooks rather than any shadow parts.