Form
Validation on blur and submit, inline errors that say how to fix them, and a success state.
ComponentsLabelInputSelectCheckbox
Preview theme
Playground
A real form with no form library. Submit it empty to see the error summary and focus jump to the first problem; fix fields and errors clear as you type. A valid submit shows a loading state, then a success panel.
Preview
Props
Layout
Validate on
Size
example.tsxtsx
1import { useInquiryForm } from "./forms/use-inquiry-form"2import { Field } from "./forms/form-field"34const form = useInquiryForm("blur", successRef)56<form noValidate onSubmit={form.submit} aria-labelledby="inquiry-title">7 {form.submitted && form.errorList.length > 0 && <ErrorSummary form={form} />}8 <fieldset className="grid gap-4 @md:grid-cols-2">9 <Field10 id="email"11 label="Work email"12 required13 helper="We reply within two working days."14 error={form.errors.email}15 >16 <Input17 id="email"18 ref={form.register("email")}19 type="email"20 autoComplete="email"21 required22 value={form.values.email}23 onChange={(e) => form.setValue("email", e.target.value)}24 onBlur={() => form.blur("email")}25 aria-invalid={form.errors.email ? true : undefined}26 aria-describedby={form.errors.email ? "email-error" : "email-helper"}27 />28 </Field>29 {/* name, company, budget (Select), timeline (fieldset + RadioGroup), message, NDA, terms */}30 </fieldset>31 <Button type="submit" disabled={form.status === "submitting"}>Send inquiry</Button>32</form>Field states
The work email field in all seven states. Errors replace the helper text in the same spot, so the layout does not shift and the message sits where the eye already is.
States
We reply within two working days.
DefaultplaceholderWe reply within two working days.
Hover:hoverWe reply within two working days.
Focus:focus-visibleChecking the domain…
Loadingaria-busy, async checkWe reply within two working days.
DisableddisabledUse your work address rather than a personal inbox.
Erroraria-invalidMatched to Northwind
Successvalidated
Keyboard
- Move to the next field
- Tab
- Choose a timeline option
- ↑or↓
- Toggle a checkbox or open the budget list
- Space
- Submit the form
- Enter
Semantics and ARIA
- Every control has a visible
<label>tied byhtmlFor; placeholders are examples, never labels. - Required fields set
requiredoraria-requiredand show an asterisk that isaria-hidden, so it is not read as "star". The form usesnoValidateso our messages replace the browser's. - Invalid controls set
aria-invalidand pointaria-describedbyat the error, which says how to fix the value rather than only that it is wrong. - On submit, a
role="alert"summary lists each problem as a link to its field, and focus moves to the first invalid field. - The timeline radios sit in a
<fieldset>with a<legend>, so the question is announced with each option. - While sending, the fieldset is disabled and the button sets
aria-busy; the success panel receives focus and is astatusregion.