Forms

A form is a Card (or a plain section), a heading, a Grid or Stack of Fields, and one primary action at the end. Submit it once with an invalid email to see validation, then with an address ending in @fail.test to see the server error.

Request a demo

We reply within one business day. Use an address ending in @fail.test to see the error state.

Optional. Two or three sentences is plenty.

Layout

  • One column by default. Put two short, related fields side by side (first and last name, city and postcode) with Grid columns={2} minItemWidth={200} — it drops to one column when there is no room.
  • Labels above controls, always visible. Placeholders are examples, never labels.
  • gap={6} between groups, gap={4} between fields. A Separator before the actions marks the end of the form.
  • Actions at the end, aligned to the end. Primary on the outside, secondary (ghost) next to it. A form has one primary button.
  • Mark required fields with required on Field; if most fields are optional, mark the required ones and say so once at the top instead.

Validation timing

WhenWhat
While typingNothing. Do not flag a field the user has not finished.
On blurValidate the field that was left, if it has a value.
On submitValidate everything, show every error, move focus to the first invalid field.
After an errorRe-validate that field on each change so the message clears as soon as it is fixed.

Field's error prop does the ARIA wiring: aria-invalid on the control and the message in aria-describedby. Moving focus to the first invalid control lets screen reader users hear the error immediately:

tsx
event.currentTarget.querySelector<HTMLElement>("[aria-invalid='true']")?.focus();

Use noValidate on the form so the browser's own bubbles do not compete with Field's messages, but keep type="email", autoComplete and inputMode — they drive mobile keyboards and autofill.

Submit states

  • Submitting. Button loading keeps its width, shows a spinner, sets aria-busy and ignores clicks. Disable the fields too, and set aria-busy on the form.
  • Server error. An Alert tone="danger" at the top of the form, with live="assertive". Keep the user's input. Say what happened and whether anything was saved.
  • Success. Either navigate away, or show an Alert tone="success" in place and reset. For small saves (settings), a toast is enough — see Settings pages.

Error messages

Say what to do, not what went wrong: "Enter a valid work email", not "Invalid input". Keep them to one line. Never clear a field because it was invalid.

With a form library

The same layout works with React Hook Form or Zod — see React Hook Form and Validation with Zod.