Components

TextField

The form-bound text input. It composes the controlled <Input> primitive and reads its field from TanStack Form context — so you never wire handleChange, invalid, or aria-describedby by hand. It maps touched + errors to the solid error styling and an error HelperText (icon and all, so the error never leans on color alone), and links label ↔ control ↔ description for you. Ships from the form layer, used inside useHaloForm.

How it works

Render it inside an form.AppField (or via the bound field.TextField). It reads the field through context — name, value, blur, errors — and owns the meta→props mapping once:

  • touched + errors → invalid (error styling, aria-invalid) and the error message in a HelperText.
  • showValid + touched + no error → valid (the friendly "looks good" state).
  • label ↔ input ↔ helper/error wired with aria-describedby and a generated id.
  • Every other Input prop — placeholder, type, leading / trailing icons — passes straight through.

Live demo

A one-field form. Type one character and watch the error show up; two or more clears it; hit submit to see the value. The styling, the helper→error swap, and the ARIA are all the field's doing.

The legal name on your charter.

Props

label: ReactNode
The field’s label — rendered as a real <label>, associated to the input for a11y. Required.
helperText?: ReactNode
A neutral hint shown below the field when there’s no error. The error message replaces it when the field is invalid.
showValid?: boolean
Opt in to the success state once the field is touched and passes — the friendly "looks good" signal.
placeholder, type, leadingIcon, …
Every Input prop except the bound ones (value, onChange, onBlur, invalid, valid, name, id, aria-describedby) passes through — so placeholder, the icon slots, and the native types all work.

Usage

A field inside useHaloFormtsx
import { Form, useHaloForm, TextField, SubmitButton, z } from '@halo-compliance/ui'

const schema = z.object({ org: z.string().min(2) })

function CreateOrg() {
  const form = useHaloForm({
    defaultValues: { org: '' },
    validators: { onChange: schema },
  })
  return (
    <Form form={form}>
      <form.AppField name="org">
        {() => <TextField label="Organization name" placeholder="Acme Lending Co." />}
      </form.AppField>
      <form.AppForm><SubmitButton>Create</SubmitButton></form.AppForm>
    </Form>
  )
}
Validation + the affirm statetsx
<form.AppField name="email">
  {() => (
    <TextField
      label="Work email"
      type="email"
      helperText="We only use this for compliance alerts."
      showValid
    />
  )}
</form.AppField>
  • /input — the primitive TextField composes (states, icon slots, the works).
  • /form — the form layer: useHaloForm, Form, SubmitButton, FormError.
  • /textarea — the multi-line sibling and its TextAreaField.
  • /helper-text — the helper / error message pattern TextField renders.