Accessibility
Form, FormErrorFocus, useFormPending from @prepared911/ui/form.form element is a landmark only when it has an accessible name, so pass aria-label or aria-labelledby.errorSummary renders as role="alert", which announces when the region appears. Its id joins the form's aria-describedby, merged with any consumer description. The form never gets aria-invalid: the attribute is not valid on the form role, so an invalid form is marked with data-invalid for styling only.aria-busy="true" on the form. The submit control carries the actionable state: Button's loading disables it and gives it a loadingLabel name. The rest of the page stays interactive.aria-invalid="true" (an invalid radiogroup yields its first focusable radio) or on the summary, which takes tabindex="-1" when it receives focus. Disabled controls are skipped.role="status"). A submit attempt does not announce each field: its focus move and the summary already speak the errors, unless the form has neither (FormErrorFocus.None, or no invalid state and no summary, so no focus move or summary follows). When the person presses a submit button with the pointer, the press blurs the field before the submit, so the blur error is held until the submit: the submit speaks it through the focus move and summary, and it is not announced a second time. When the submit has no focus move or summary to speak it, the held error is announced as the submit is handled. A press that does not end in a submit (the pointer leaves the button, or the error moves the button away) releases the held error shortly after. Keyboard submits do not blur the field first. A cleared error announces nothing, and an error already showing when the form mounts is not announced. Every control that draws its own error does this, the multiple Combobox included. Write error copy that names the field ("Enter an email address", not "Required"), because the announcement is read after focus has moved on. announceFieldErrors={false} turns this off when the page announces validation itself.aria-invalid on failing controls and use errorSummary. With noValidate={false} the browser's validation bubbles apply.