# Field

> A compound wrapper for labelled form fields with helper and error text

Status: stable. Added in Components 2.0.0. Updated in Components 2.16.0, 20 Oct 2026.

A compound wrapper for labelled form fields with helper and error text.

## Import

```tsx
import { Field } from '@oztix/roadie-components/field'
```

## Examples

### Default

```tsx
<Field>
  <Field.Label>Name</Field.Label>
  <Field.Input placeholder='Enter your name' />
</Field>
```

### With indicators

Use `showIndicator` on `Field.Label` to automatically render a required or
optional indicator based on the Field's `required` prop. This eliminates the
need to manually import and place `RequiredIndicator` or `OptionalIndicator`.

```tsx
<Field required>
  <Field.Label showIndicator>Email</Field.Label>
  <Field.Input type='email' placeholder='you@example.com' />
</Field>
<Field>
  <Field.Label showIndicator>Phone number</Field.Label>
  <Field.Input type='tel' placeholder='0400 000 000' />
</Field>
```

### States

Fields inherit input interaction states. Focus shows accent colouring,
invalid shows danger colouring with error text. The `invalid` and `disabled`
props flow through context to child inputs automatically.

```tsx
{/* Default */}
<Field>
  <Field.Label>Label</Field.Label>
  <Field.Input placeholder='Click to focus' />
  <Field.HelperText>Helper text appears below the input.</Field.HelperText>
</Field>
{/* Invalid */}
<Field invalid>
  <Field.Label>Label</Field.Label>
  <Field.Input defaultValue='bad-value' />
  <Field.ErrorText>This field is required.</Field.ErrorText>
</Field>
{/* Disabled */}
<Field disabled>
  <Field.Label>Label</Field.Label>
  <Field.Input placeholder='Cannot edit' />
</Field>
```

### Composition

Field exposes sub-components for label, input, textarea, helper text, and error text.

#### With helper text

```tsx
<Field>
  <Field.Label>Email</Field.Label>
  <Field.Input type='email' placeholder='you@example.com' />
  <Field.HelperText>We'll never share your email.</Field.HelperText>
</Field>
```

#### With error text

Set `invalid` on the Field root to activate the danger state. The input
border and focus ring automatically switch to danger colours, and
`aria-invalid` is set on the input automatically.

```tsx
<Field invalid>
  <Field.Label>Password</Field.Label>
  <Field.Input type='password' />
  <Field.ErrorText>Use at least eight characters.</Field.ErrorText>
</Field>
```

Controls that read typed text, such as [DateField](https://ticketsolutionsptyltd.github.io/roadie/components/date-field.md), also show their own error in `Field.ErrorText` when the text names nothing, such as "Enter a date, like 14 Mar or next Fri". It shows even when `invalid` isn't set, and wins over the error text's children until the text is fixed.

#### With textarea

```tsx
<Field>
  <Field.Label>Bio</Field.Label>
  <Field.Textarea placeholder='Tell us about yourself' />
  <Field.HelperText>Maximum 200 characters.</Field.HelperText>
</Field>
```

## Guidelines

Write labels, helper text, and error text with
[Interface copy](https://ticketsolutionsptyltd.github.io/roadie/content/interface-copy.md#form-help).

## Accessibility

- **Auto-wired IDs**: Field auto-generates matching IDs for the label, input, helper text, and error text. You don't need to set `htmlFor` or `id` manually.
- **Context-driven aria**: The `invalid`, `required`, and `disabled` props on Field automatically set `aria-invalid`, `aria-required`, and `disabled` on child inputs via context.
- **Error announcements**: `Field.ErrorText` renders with `role="alert"` and only appears when `invalid` is true or a control reports its own error, so screen readers announce errors immediately.
- **Description linking**: `aria-describedby` is automatically wired to the helper text (or error text when invalid).

## Hooks

### useFieldContext

```tsx
function useFieldContext(): {
  invalid?: boolean
  required?: boolean
  disabled?: boolean
  fieldId: string
  labelId: string
  helperTextId: string
  errorTextId: string
}
```

Reads the nearest `Field`'s state. Use it when building a custom control that
needs to react to the same `invalid` / `required` / `disabled` state as
`Field.Input`, without reimplementing Field's own sub-components. Outside a
`Field`, every value comes back empty or `undefined`.

| Field          | Type                   | Description                              |
| -------------- | ---------------------- | ---------------------------------------- |
| `invalid`      | `boolean \| undefined` | Whether the field is invalid.            |
| `required`     | `boolean \| undefined` | Whether the field is required.           |
| `disabled`     | `boolean \| undefined` | Whether the field is disabled.           |
| `fieldId`      | `string`               | The id wired to the input and its label. |
| `labelId`      | `string`               | The label's id.                          |
| `helperTextId` | `string`               | `Field.HelperText`'s id.                 |
| `errorTextId`  | `string`               | `Field.ErrorText`'s id.                  |

### useFieldInputProps

```tsx
function useFieldInputProps(): {
  id: string | undefined
  disabled: boolean | undefined
  'aria-invalid': boolean | undefined
  'aria-required': boolean | undefined
  'aria-describedby': string | undefined
}
```

Derives the DOM attributes a custom input needs from the nearest `Field`'s
context: this is what `Field.Input` and `Field.Textarea` spread onto their own
element. Reach for it when building a control `Field.Input` doesn't cover.

| Field              | Type                   | Description                                                       |
| ------------------ | ---------------------- | ----------------------------------------------------------------- |
| `id`               | `string \| undefined`  | Matches `Field.Label`'s `htmlFor`.                                |
| `disabled`         | `boolean \| undefined` | The field's `disabled` state.                                     |
| `aria-invalid`     | `boolean \| undefined` | The field's `invalid` state.                                      |
| `aria-required`    | `boolean \| undefined` | The field's `required` state.                                     |
| `aria-describedby` | `string \| undefined`  | The error text's id when invalid, otherwise the helper text's id. |

### useFieldControlError

```tsx
function useFieldControlError(message: string | null): void
```

Shows a control's own error in `Field.ErrorText` while the control is
mounted, such as a date it can't read or a file it couldn't upload. Pass
`null` once the problem is fixed. The control's message wins over the
error text's children, and shows even when the field isn't `invalid`. With
several controls in one field, the first to mount shows its error first.
Outside a `Field` it does nothing. It returns nothing.

It doesn't mark the control itself, and `useFieldInputProps` follows only the
field's `invalid`. While the control has an error, set `aria-invalid` on it
and point its `aria-describedby` at `errorTextId` from `useFieldContext`, as
Roadie's date and time fields do.

## API reference

### Field

```tsx
import { Field } from '@oztix/roadie-components/field'
```

- `invalid`: `boolean`.
- `required`: `boolean`.
- `disabled`: `boolean`.

### Field.ErrorText

Shows when the field is invalid, or when its control can't read what was typed. The control's message, such as a date it can't read, wins over `children`, since it says exactly what to fix.

No props beyond the standard HTML attributes.

### Field.HelperText

No props beyond the standard HTML attributes.

### Field.Input

- `intent`: `"neutral" | "brand" | "brand-secondary" | "brand-blue" | "brand-orange" | "brand-purple" | "accent" | "danger" | "success" | "warning" | "info" | null`. Deprecated: Form controls take their colour from state; `is-interactive-field` handles it. Will be removed in v3.0.0.
- `size`: `"sm" | "md" | "lg" | null`.
- `emphasis`: `"normal" | "subtle" | null`.

### Field.Label

- `showIndicator`: `boolean`.

### Field.Textarea

- `intent`: `"neutral" | "brand" | "brand-secondary" | "brand-blue" | "brand-orange" | "brand-purple" | "accent" | "danger" | "success" | "warning" | "info" | null`. Deprecated: Form controls take their colour from state; `is-interactive-field` handles it. Will be removed in v3.0.0.
- `autoResize`: `boolean`.
- `size`: `"sm" | "md" | "lg" | null`.
- `emphasis`: `"normal" | "subtle" | null`.

Docs updated 19 Oct 2026.
