Field · Input Atom

Field owns the wiring that is most often silently wrong: the label pointing at the control, the hint and error being announced through aria-describedby, and aria-invalid matching the visible error state. It owns nothing else — no validation, no form state.

Usage

We only use this for receipts.

tsx · motion on
<Field label="Work email" hint="We only use this for receipts."><Input type="email" name="email" placeholder="you@company.com" /></Field>

Errors

A string renders the message and marks the control invalid. error as a bare true marks it invalid without a message — for the case where the message lives somewhere else on the page.

We only use this for receipts.

tsx · motion on
<Field label="Work email" hint="We only use this for receipts." error="That address is already registered."><Input type="email" defaultValue="ada@company.com" /></Field>

Do

Write the error as what to do next: 'That address is already registered.'

Don't

Write 'Invalid input' — it passes the aria wiring and still tells the person nothing.

With a form library

The library is deliberately neutral. Nothing below is an adapter; Input simply forwards its ref and props.

Same component, three different form stories.

tsx · motion on
// react-hook-form<Field label="Email" error={errors.email?.message}><Input {...register('email')} /></Field>
// Next Server Action — no client form state at all<form action={createUser}><Field label="Email"><Input name="email" required /></Field></form>

Props

PropTypeDefaultNotes
labelReactNodeRequired. Use labelHidden rather than dropping it.
hintReactNodeGuidance shown before the person makes a mistake.
errorReactNode | trueA message, or true for invalid-without-message.
requiredbooleanfalseMarks the label and forwards required to the control.
labelHiddenbooleanfalseVisually hidden, still announced.

Accessibility notes

  • The error is rendered with role="alert", so it is announced when it appears.
  • aria-describedby lists hint and error in reading order, not just the last one.
  • A Input used outside a Field stays unwired rather than inventing ids — pass aria-label.