Field
Puts a label, a hint and any error around a control.
Examples
Lowercase letters, digits and dashes.
Use lowercase letters and dashes.
Orientation
Lowercase and dashes.
verticaldefault
Reply to customers and issue refunds.
horizontal
responsive
States
Lowercase and dashes.
rest
Lowercase and dashes.
focus-visible
- Use lowercase letters.
invalid
Locked while live.
disabled
Usage
When to use it
- Use
- Any control the person fills: Input, Textarea, Combobox, Date picker, Select.
- Use
- A Switch or Checkbox that needs a sentence of explanation beside it.
- Use
- A set of fields that submit together, wrapped in Form.
- Not for
- A search box in a toolbar names itself with aria-label. Use Input.
- Not for
- A row of filters, not a form. Use Filter bar.
- Not for
- A message about the whole page or account. Use Alert.
Rules
8 rules
- Must
- Leave validationMode unset so a field checks on blur, then on change while invalid. Because onChange flags the value as wrong on the first keystroke, before the person has finished typing it.
- Must
- Wrap fields that submit together in Form with a type="submit" Button, and pass pending to both. Because without Form, Enter in a field submits nothing and a second press sends the request twice.
- Must
- Pass server rejections to Form errors keyed by field name, not to a toast. Because a toast disappears and is tied to no control, while Form errors mark the field invalid under its own label.
- Must
- Name a control with FieldLabel inside a Field, or Label htmlFor outside one, never a styled p. Because a p is not a label, so the control has no accessible name and a click on the text does not focus it.
- Should
- Write an error as what to do, in a sentence with a period.
- Should
- Mark the few optional fields with optional and mark nothing else.
- Never
- Style an error as raw red text; use FieldError. Because a loose p is not in the control's aria-describedby, so a screen reader never reads it, and red-600 is not the fail text step.
- Should
- Keep a FieldDescription to one sentence under 120 characters.
Accessibility
5 checks
- FieldLabel is a native label bound to the control; a click on it focuses the control.
- FieldDescription and FieldError link by aria-describedby; the control gets aria-invalid.
- Tab leaves the control and validates it; Enter in a Form submits, and does nothing while pending.
- Form sets aria-busy while pending; FormStatus is a polite status region.
- The error is text, so it survives forced colors.
Edge cases
10 edge cases
- Long label and error
- Both wrap; nothing has a fixed height.
- Right to left
- Label, hint and error align to the inline start; FieldSeparator centres logically.
- Inside a disabled FieldSet
- Every Field and the buttons in its controls take the disabled state.
- 120px wide
- Label, control and error stack; responsive stays vertical.
- No FieldLabel
- The control is unnamed; add the label or aria-label.
- Disabled and invalid
- Disabled wins; validation is skipped and the error hides.
- Server and client error
- FieldError dedupes and lists both.
- Enter three times while pending
- One request is sent; Form drops the rest.
- Failed submit
- Values stay and the first invalid field takes focus.
- Zoom 200%
- Label, hint and error wrap and grow.
API
7 props
Fieldname, invalid, validate, validationMode, disabled, orientationFieldLabeloptionalFieldErrorerrors, match, childrenFieldSet, FieldLegenddisabled; variantFormpending, errors, onFormSubmitFormStatusstatus: 'ok' | 'fail'Label, LabelHintoptional, htmlFor
Tokens
11 tokens
--type-label- 400 14px/16px var(--font-sans)
--text-1- var(--ink)
--text-disabled- oklch(var(--ink-c) / 0.40)
--type-body-sm- 380 13px/18px var(--font-sans)
--text-2- oklch(var(--ink-c) / 0.80)
--fail-text- oklch(51% 0.180 26)
--line-control- oklch(var(--ink-c) / 0.16)
--line-frame- oklch(var(--ink-c) / var(--line-frame-alpha))
--accent-wash- var(--surface-selected)
--line-focus- 2px
--border-focus- var(--ink)