# Field

**A form field wrapper that provides accessible labeling, helper and error text, validation state and indicators for form inputs.**

**Reference:** [View Source](https://github.com/jramke/fluid-primitives/tree/main/Resources/Private/Primitives/Field)

**FieldExamples.html**

```html
<ui:field.root class="max-w-74" name="username" required="{true}">
    <ui:input.root type="text" autocomplete="username">
        <ui:input.label>Username</ui:input.label>
        <ui:input.input />
    </ui:input.root>
    <ui:field.helperText>Choose a unique username for your account.</ui:field.helperText>
    <ui:field.errorText />
</ui:field.root>
```

## Features

- Automatic label association with form controls
- Helper text and error text, linked to the control with the right ARIA attributes
- Reads the browser's own constraint validation: `required`, `pattern`, `type="email"`, `min`/`max`, `minlength`/`maxlength` and more decide whether a field is valid, and error texts can be narrowed to the constraint that failed
- Custom validation per field, synchronous or asynchronous, next to the schema or callback validation of the [Form](/docs/components/form.md) - all of them end up in the same place
- Validation modes decide when errors become visible, and an error that is showing always follows the user while they fix it
- State flags for touched, dirty, filled, focused, valid, invalid, disabled, required and read-only, mirrored as data attributes on every part
- Indicators for the required, invalid, valid and validating state
- Disabled by a surrounding [Fieldset](/docs/components/fieldset.md) or `<fieldset disabled>`
- Works with all form-related primitives (Checkbox, Select, RadioGroup, etc.) and native inputs

## Anatomy

```html
<primitives:field.root>
    <primitives:field.label />
    <primitives:field.control asChild="{true}">
        <!-- Your form input here -->
    </primitives:field.control>
    <primitives:field.indicator type="required" />
    <primitives:field.helperText />
    <primitives:field.errorText />
</primitives:field.root>
```

## Installation

```bash
typo3 ui:add field
```

Or copy the files manually from GitHub (https://github.com/jramke/fluid-primitives/tree/main/Resources/Private/Registry/Field) into your project.

Read more about installing [Components and Primitives](/docs/core-concepts/primitives.md).

## Examples

### Required Field

Mark a field as required. An empty required field stays quiet until the user has edited it or the form is submitted.

**FieldExamples.html**

```html
<ui:field.root class="max-w-74" name="name" required="{true}">
    <ui:input.root type="text">
        <ui:input.label>Full Name</ui:input.label>
        <ui:input.input />
    </ui:input.root>
    <ui:field.errorText />
</ui:field.root>
```

### With Helper Text

Add helpful text below the input. It is linked to the control with `aria-describedby`.

**FieldExamples.html**

```html
<ui:field.root class="max-w-74" name="password">
    <ui:input.root type="password">
        <ui:input.label>Password</ui:input.label>
        <ui:input.input />
    </ui:input.root>
    <ui:field.helperText>Must be at least 8 characters long.</ui:field.helperText>
    <ui:field.errorText />
</ui:field.root>
```

### Invalid State

Indicate that the field has an error and display an error message.

**FieldExamples.html**

```html
<ui:field.root class="max-w-74" name="password" required="{true}" invalid="{true}">
    <ui:input.root type="password">
        <ui:input.label>Password</ui:input.label>
        <ui:input.input />
    </ui:input.root>
    <ui:field.errorText>The password must be at least 8 characters long.</ui:field.errorText>
</ui:field.root>
```

### Disabled Field

Disable the entire field.

**FieldExamples.html**

```html
<ui:field.root class="max-w-74" name="readonly-value" disabled="{true}">
    <ui:input.root type="text" defaultValue="Cannot be edited">
        <ui:input.label>Disabled Input</ui:input.label>
        <ui:input.input />
    </ui:input.root>
</ui:field.root>
```

### With Checkbox

Use with the Checkbox component.

**FieldExamples.html**

```html
<ui:field.root class="max-w-74" name="terms" required="{true}">
    <ui:checkbox.root>
        <ui:checkbox.control />
        <ui:checkbox.label>I accept the terms and conditions</ui:checkbox.label>
    </ui:checkbox.root>
    <ui:field.errorText />
</ui:field.root>
```

### Native Validation

The field reads the `ValidityState` of its control, so the constraints you already know are validation rules: `required` (the prop of the field, not an attribute), `type="email"` and the like on the input, and `pattern`, `min`, `max`, `step`, `minlength` and `maxlength`, which you pass to the input part as plain attributes.

The `form` renders `novalidate`, so the browser never shows its own bubble. Instead, an error text without a `match` shows the message of the failed constraint, in the language of the browser. Give it a `match` to show your own text for one constraint, e.g. `valueMissing`, `typeMismatch`, `patternMismatch`, `tooShort`, `tooLong`, `rangeUnderflow`, `rangeOverflow`, `stepMismatch`, `badInput` or `customError`. An error text with a `match` stays hidden until exactly that constraint failed.

**FieldExamples.html**

```html
<ui:form.root actionUri="#" class="w-full max-w-74">
    <ui:form.content class="space-y-6">
        <ui:field.root name="email" required="{true}">
            <ui:input.root type="email" autocomplete="email">
                <ui:input.label>Email</ui:input.label>
                <ui:input.input />
            </ui:input.root>
            <ui:field.errorText match="{f:constant(name: 'Jramke\FluidPrimitives\Enum\ValidityMatch::ValueMissing')}">Please enter your email address.</ui:field.errorText>
            <ui:field.errorText match="{f:constant(name: 'Jramke\FluidPrimitives\Enum\ValidityMatch::TypeMismatch')}">That doesn't look like an email address.</ui:field.errorText>
        </ui:field.root>

        <ui:field.root name="zip">
            <ui:input.root type="text" inputmode="numeric" autocomplete="postal-code">
                <ui:input.label>ZIP code</ui:input.label>
                <ui:input.input pattern="[0-9]+" minlength="5" maxlength="5" />
            </ui:input.root>
            <ui:field.helperText>Five digits.</ui:field.helperText>
            <ui:field.errorText match="{f:constant(name: 'Jramke\FluidPrimitives\Enum\ValidityMatch::PatternMismatch')}">A ZIP code only has digits.</ui:field.errorText>
            <ui:field.errorText match="{f:constant(name: 'Jramke\FluidPrimitives\Enum\ValidityMatch::TooShort')}">A ZIP code has five digits.</ui:field.errorText>
        </ui:field.root>

        <ui:button type="submit">Submit</ui:button>
    </ui:form.content>
</ui:form.root>

```

> **Where the value comes from** - A field validates the value of the primitive inside it, not only native inputs: a required Select, Combobox, Checkbox, RadioGroup or CheckboxGroup counts as missing while nothing is chosen. The other constraints come from the native control, so they apply to inputs and textareas.

### Validation Mode

`validationMode` decides when a field validates and shows its errors:

- `onBlur` (the default) validates when the user leaves a field they have edited. Tabbing through untouched fields stays quiet.
- `onSubmit` shows errors after the first submit attempt, and from then on as the user types.
- `onChange` validates on every change.

In every mode, an error that is already showing is validated again on every change, so the message goes away as soon as the user has fixed the value, instead of waiting for the next blur. A submit validates all fields, shows every error and moves the focus to the first invalid one.

Error texts and indicators are never shown or hidden while a mouse button or a finger is down. A field blurs on the press that leaves it, and an error text appearing at that moment would move the button the user is about to release on, so the click would be lost. They wait for the release instead (keyboard users get them right away).

**FieldExamples.html**

```html
<ui:form.root actionUri="#" class="w-full max-w-74">
    <ui:form.content class="space-y-6">
        <ui:field.root name="onSubmit" required="{true}" validationMode="{f:constant(name: 'Jramke\FluidPrimitives\Enum\ValidationMode::OnSubmit')}">
            <ui:input.root type="text">
                <ui:input.label>On submit</ui:input.label>
                <ui:input.input minlength="3" />
            </ui:input.root>
            <ui:field.helperText>Errors show after the first submit.</ui:field.helperText>
            <ui:field.errorText />
        </ui:field.root>

        <ui:field.root name="onBlur" required="{true}" validationMode="{f:constant(name: 'Jramke\FluidPrimitives\Enum\ValidationMode::OnBlur')}">
            <ui:input.root type="text">
                <ui:input.label>On blur (default)</ui:input.label>
                <ui:input.input minlength="3" />
            </ui:input.root>
            <ui:field.helperText>Errors show when you leave an edited field.</ui:field.helperText>
            <ui:field.errorText />
        </ui:field.root>

        <ui:field.root name="onChange" required="{true}" validationMode="{f:constant(name: 'Jramke\FluidPrimitives\Enum\ValidationMode::OnChange')}">
            <ui:input.root type="text">
                <ui:input.label>On change</ui:input.label>
                <ui:input.input minlength="3" />
            </ui:input.root>
            <ui:field.helperText>Errors show while you type.</ui:field.helperText>
            <ui:field.errorText />
        </ui:field.root>

        <ui:button type="submit">Submit</ui:button>
    </ui:form.content>
</ui:form.root>

```

### Indicators

An `indicator` shows its content while the field is in one state, and stays hidden otherwise. Its `type` is one of `required`, `invalid`, `valid` and `validating`. `valid` appears once the field has been validated and passed.

**FieldExamples.html**

```html
<ui:field.root class="max-w-74" name="handle" required="{true}">
    <ui:input.root type="text">
        <ui:input.label>Handle</ui:input.label>
        <ui:input.input pattern="[a-z0-9_]+" minlength="3" />
    </ui:input.root>
    <div class="flex items-center gap-3">
        <ui:field.indicator type="{f:constant(name: 'Jramke\FluidPrimitives\Enum\FieldIndicatorType::Required')}">Required</ui:field.indicator>
        <ui:field.indicator type="{f:constant(name: 'Jramke\FluidPrimitives\Enum\FieldIndicatorType::Valid')}">Looks good</ui:field.indicator>
        <ui:field.indicator type="{f:constant(name: 'Jramke\FluidPrimitives\Enum\FieldIndicatorType::Invalid')}">Not valid</ui:field.indicator>
    </div>
    <ui:field.helperText>At least three lowercase letters, digits or underscores.</ui:field.helperText>
    <ui:field.errorText />
</ui:field.root>

```

### Async Validation

A field can run its own check, for example to ask the server whether a username is still free. Pass a `validate` function that returns a message (or a list of messages) to mark the field invalid, and `null` to accept the value. It may return a promise.

A function cannot travel through the server-rendered markup, so pass it from TypeScript: render the field with `autoMount="{false}"`, and mount it yourself. While the promise is pending the field is `validating` (use the `validating` indicator), a result for a value the user has changed meanwhile is dropped, and a submit waits for it.

`validate` receives the `value` and a snapshot of the native `validity`. It runs at the same points that make an error visible (see the validation mode), not on every keystroke, so it is a good place for a request.

**FieldExamples.html**

```html
<ui:exposeToClient />

<ui:field.root class="max-w-74" name="username" required="{true}" autoMount="{false}" rootId="username-availability">
    <ui:input.root type="text" autocomplete="username">
        <ui:input.label>Username</ui:input.label>
        <ui:input.input minlength="3" />
    </ui:input.root>
    <div class="flex items-center gap-3 text-sm">
        <ui:field.indicator type="{f:constant(name: 'Jramke\FluidPrimitives\Enum\FieldIndicatorType::Validating')}">Checking...</ui:field.indicator>
        <ui:field.indicator type="{f:constant(name: 'Jramke\FluidPrimitives\Enum\FieldIndicatorType::Valid')}">Available</ui:field.indicator>
    </div>
    <ui:field.helperText>Try "admin" or "ada".</ui:field.helperText>
    <ui:field.errorText />
</ui:field.root>

<vite:asset entry="EXT:docs/Resources/Private/Components/ui/FieldExamples/AsyncValidation.entry.ts" />

```

**AsyncValidation.entry.ts**

```ts
import { mount, mountAll } from 'fluid-primitives';
import { Field } from 'fluid-primitives/field';

const taken = ['admin', 'root', 'ada'];

mountAll('ui:fieldExamples.asyncValidation', () => {
    mount('ui:field', 'username-availability', ({ props }) => {
        const field = new Field({
            ...props,
            validate: async ({ value, validity }) => {
                // only ask once the browser's own checks (required, minlength, ...) pass
                if (!validity.valid) return null;

                // stands in for a request, e.g. `extbase.get(...)`
                await new Promise(resolve => setTimeout(resolve, 700));
                return taken.includes(value.toLowerCase()) ? `"${value}" is already taken.` : null;
            },
        });
        field.init();
        return field;
    });
});

```

Use `updateProps({ validate })` to replace the function later. The schema or callback of the `Form` and the errors the server sent back (422 or from `onSubmit`) do not need any of this, they reach the field on their own and are shown in the same error text.

### Linked Fields

A field can react to a _different_ field's value changing via the `listenTo` prop - names of sibling fields within the same form to watch. Two things happen whenever a listened-to field changes its value: a `fluid-primitives:field:dependencychange` event is dispatched on the root element of this field, for any imperative DOM reaction you need, and this field is treated as if its own value had changed. It validates again under the same rules as when its own user types: immediately if it shows an error (or its `validationMode` is `onChange`, or the form was submitted before), otherwise at its next blur or submit.

`FieldHandle` (what both `form.api.getField(name)` and a `Field` instance's own `.api` return) exposes `addDependencyChangeListener(callback)` for the first half - it registers `callback` and returns a function that removes it again, the same "call it to start listening, get a cleanup function back" shape as `addEventListener`/`Machine.subscribe`, rather than a config-style `onX` prop you'd set once:

```typescript
const needsStudentId = (values: FormValues) => values.get('ticketType') === 'student';
const studentIdField = form.api.getField('studentId')!;

const applyStudentIdVisibility = (show: boolean) => {
    studentIdField.getRootEl()!.hidden = !show;
    studentIdField.setDisabled(!show);
};

// the same rule decides the starting state and every later change
applyStudentIdVisibility(needsStudentId(form.api.getValues()));
studentIdField.addDependencyChangeListener(({ values }) => {
    applyStudentIdVisibility(needsStudentId(values));
});
```

The callback receives `dependencies` (the current value of each listened-to field, keyed by name) and `values`, the current `FormValues` of the whole form, which is what a rule shared with your `validation` callback usually wants.

See the [Complete Example: Event Registration](/docs/core-concepts/forms.md#complete-example-event-registration) in the Forms guide for this exact pattern used to show/hide a `studentId` field based on `ticketType`, in place of recomputing it inside the form's own `render` callback on every field change.

The classic case is a password confirmation field - `passwordConfirm` listens to `password`, so changing `password` validates `passwordConfirm` again, instead of leaving a stale result:

```html
<ui:field.root name="password">
    <ui:input.root type="password">
        <ui:input.label>Password</ui:input.label>
        <ui:input.input />
    </ui:input.root>
    <ui:field.errorText />
</ui:field.root>

<ui:field.root name="passwordConfirm" listenTo="{0: 'password'}">
    <ui:input.root type="password">
        <ui:input.label>Confirm password</ui:input.label>
        <ui:input.input />
    </ui:input.root>
    <ui:field.errorText />
</ui:field.root>
```

No consumer JavaScript is needed for the _re-triggering_ itself - `listenTo` handles that. You still need to decide what "valid" means, though, and a manual `validation` callback (see [Manual Client-Side Validation](/docs/core-concepts/forms.md#manual-client-side-validation)) fits better here than a single schema: checking each field independently means `passwordConfirm`'s mismatch error can appear even while `password` hasn't yet satisfied its own length requirement, whereas a single Zod schema with `.refine()` would silently skip that cross-field check until the base shape (including `password`'s own `min(8)`) validates first:

```typescript
validation: ({ values }) => {
    const errors: Record<string, { messages: string[] }> = {};

    const password = values.get('password');
    if (typeof password !== 'string' || password.length < 8) {
        errors.password = { messages: ['Password must be at least 8 characters'] };
    }

    const passwordConfirm = values.get('passwordConfirm');
    if (passwordConfirm !== null && passwordConfirm !== password) {
        errors.passwordConfirm = { messages: ['Passwords do not match'] };
    }

    return errors;
},
```

**PasswordConfirmation.html**

```html
<ui:exposeToClient />

<ui:form.root actionUri="#" autoMount="{false}" rootId="{rootId}-form" class="w-full max-w-74">
    <ui:form.content class="space-y-6">
        <ui:field.root name="password" required="{true}">
            <ui:input.root type="password" autocomplete="new-password">
                <ui:input.label>Password</ui:input.label>
                <ui:input.input />
            </ui:input.root>
            <ui:field.helperText>At least 8 characters.</ui:field.helperText>
            <ui:field.errorText />
        </ui:field.root>

        <ui:field.root name="passwordConfirm" required="{true}" listenTo="{0: 'password'}">
            <ui:input.root type="password" autocomplete="new-password">
                <ui:input.label>Confirm password</ui:input.label>
                <ui:input.input />
            </ui:input.root>
            <ui:field.errorText />
        </ui:field.root>

        <ui:button type="submit">Save password</ui:button>
    </ui:form.content>

    <ui:form.indicator
        state="{f:constant(name: 'Jramke\FluidPrimitives\Enum\FormState::Success')}"
        class="space-y-3">
        <ui:form.successText class="block">Password updated successfully.</ui:form.successText>
        <ui:button type="reset">Reset</ui:button>
    </ui:form.indicator>
</ui:form.root>

<vite:asset entry="EXT:docs/Resources/Private/Components/PasswordConfirmation/PasswordConfirmation.entry.ts" />
```

**PasswordConfirmation.entry.ts**

```ts
import { getHydrationData, mountAll } from 'fluid-primitives';
import { Form } from 'fluid-primitives/form';

// A manual callback rather than a single Zod schema with `.refine()` - a `.refine()` only runs
// once the base object shape has already validated, so a too-short `password` would silently
// skip the cross-field "do they match" check entirely (passwordConfirm would never get an error
// while the user is still typing a short password). Checking each independently means
// passwordConfirm's mismatch error can surface regardless of whether password also satisfies its
// own length requirement yet. listenTo on the passwordConfirm field (see the template) does all
// the cross-field re-triggering here - this callback only needs to describe what "valid" means.
mountAll('ui:passwordConfirmation', ({ props }) => {
    const data = getHydrationData('ui:form', props.id + '-form');
    if (!data) return;

    const form = new Form({
        ...data.props,
        validation: ({ values }) => {
            const errors: Record<string, { messages: string[] }> = {};

            const password = values.get('password');
            if (typeof password !== 'string' || password.length < 8) {
                errors.password = { messages: ['Password must be at least 8 characters'] };
            }

            const passwordConfirm = values.get('passwordConfirm');
            if (passwordConfirm !== null && passwordConfirm !== password) {
                errors.passwordConfirm = { messages: ['Passwords do not match'] };
            }

            return errors;
        },
        onSubmit: async ({ values }) => {
            alert(JSON.stringify(values.toObject()));
            return true;
        },
    });

    form.init();
});

```

> **Multiple dependencies** - listenTo accepts more than one name, e.g. listenTo="{0: 'a', 1: 'b'}" - the dependencychange event's dependencies object then carries every listened field's current value, keyed by name.

## State and Data Attributes

Every part of a field renders the state of the field as data attributes, so a label, an indicator or an error text can be styled without any JavaScript. The server renders the ones that follow from the props (`data-disabled`, `data-invalid`, `data-required`, `data-readonly`), the others are set once the field is hydrated. The [API reference](#content-api-reference) lists them per part.

The same flags are available from TypeScript on `field.api` (and on the `FieldHandle` of `form.api.getField(name)`): `touched`, `dirty`, `filled`, `focused`, `valid`, `invalid`, `validating`, `disabled`, `required`, `readOnly` and the `errors` list, together with `validate()`, `clearErrors()` and `reset()`. A `fluid-primitives:field:valuechange` event bubbles from the root of the field whenever its value settled on a new one, 100 ms after the last keystroke by default.

## API Reference

### field.root

Provides shared field state for labels, helper text, errors, indicators and controls. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `name` | `string` | Yes | `-` | The unique field name used to associate labels, helper text, errors, and form values. |
| `disabled` | `boolean` | No | `-` | Whether the field is disabled. A field inside a disabled `<fieldset>` is disabled as well. |
| `invalid` | `boolean` | No | `-` | Whether the field is invalid, regardless of validation. Use it for errors the browser's constraint validation and the form's validation cannot compute, e.g. a pre-populated server error. |
| `required` | `boolean` | No | `-` | Whether the field is required. An empty required field stays quiet until the user edits it or the form is submitted. |
| `readOnly` | `boolean` | No | `-` | Whether the field is read-only. |
| `defaultValue` | `mixed` | No | `-` | The initial value that child controls can use when rendered inside the field. |
| `validationMode` | `Enum\ValidationMode` | No | `OnBlur` | When errors become visible. `onBlur` validates on blur once the value was edited, `onSubmit` after the first submit attempt, `onChange` on every change. An error that is showing always revalidates on change, in every mode. |
| `listenTo` | `string[]` | No | `-` | Names of sibling fields within the same form whose value changes this field should react to. Dispatches a `fluid-primitives:field:dependencychange` event on this field's root element (see `addDependencyChangeListener()`) on every value change of a listened-to field, and treats that like a change of this field itself, so it revalidates under the same rules as when its own value changes - e.g. a `passwordConfirm` field listening to `password`. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `rootId` | `string` | No | `-` | The root ID of the component, used for hydration and identification. |
| `ids` | `array` | No | `[]` | The IDs of of the component parts for composition. |
| `autoMount` | `boolean` | No | `true` | Whether the client initializes the component on its own. Set it to false to mount it yourself with `mount()` |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-disabled` | Present when the field is disabled, or it is inside a disabled fieldset. |
| `data-invalid` | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
| `data-valid` | Present when the field has been validated and passed. |
| `data-required` | Present when the field is required. |
| `data-readonly` | Present when the field is read-only. |
| `data-touched` | Present once the user has left the field at least once. |
| `data-dirty` | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
| `data-filled` | Present when the field has a value. |
| `data-focus` | Present when focus is inside the field. |
| `data-name` | The name of the field. |

### field.label

Labels the associated form control. Renders a `<label>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-disabled` | Present when the field is disabled, or it is inside a disabled fieldset. |
| `data-invalid` | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
| `data-valid` | Present when the field has been validated and passed. |
| `data-required` | Present when the field is required. |
| `data-readonly` | Present when the field is read-only. |
| `data-touched` | Present once the user has left the field at least once. |
| `data-dirty` | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
| `data-filled` | Present when the field has a value. |
| `data-focus` | Present when focus is inside the field. |

### field.control

Wraps the slotted form control and wires up shared field attributes. Renders the element defined by the `asChild` prop.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-disabled` | Present when the field is disabled, or it is inside a disabled fieldset. |
| `data-invalid` | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
| `data-valid` | Present when the field has been validated and passed. |
| `data-required` | Present when the field is required. |
| `data-readonly` | Present when the field is read-only. |
| `data-touched` | Present once the user has left the field at least once. |
| `data-dirty` | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
| `data-filled` | Present when the field has a value. |
| `data-focus` | Present when focus is inside the field. |

### field.helperText

Displays help or supporting text for the field. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-disabled` | Present when the field is disabled, or it is inside a disabled fieldset. |
| `data-invalid` | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
| `data-valid` | Present when the field has been validated and passed. |
| `data-required` | Present when the field is required. |
| `data-readonly` | Present when the field is read-only. |
| `data-touched` | Present once the user has left the field at least once. |
| `data-dirty` | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
| `data-filled` | Present when the field has a value. |
| `data-focus` | Present when focus is inside the field. |

### field.errorText

Displays the validation message of the field, or the text of its `match` when one constraint failed. Renders a `<div>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `match` | `Enum\ValidityMatch` | No | `-` | Narrows the error text to one failed native constraint, e.g. `valueMissing` or `typeMismatch`. Without it the text shows whenever the field is invalid and displays the error messages; with it the slotted text is shown as is. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-disabled` | Present when the field is disabled, or it is inside a disabled fieldset. |
| `data-invalid` | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
| `data-valid` | Present when the field has been validated and passed. |
| `data-required` | Present when the field is required. |
| `data-readonly` | Present when the field is read-only. |
| `data-touched` | Present once the user has left the field at least once. |
| `data-dirty` | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
| `data-filled` | Present when the field has a value. |
| `data-focus` | Present when focus is inside the field. |

### field.indicator

Shows its content while the field is in the state given by `type`: `required`, `invalid`, `valid` or `validating`. Renders a `<span>` element.

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `type` | `Enum\FieldIndicatorType` | Yes | `-` | The state the indicator reflects: `required`, `invalid`, `valid` or `validating`. It stays in the DOM and is hidden unless the state matches, so CSS transitions survive. |
| `attributes` | `array` | No | `[]` | Additional attributes that should be rendered on the component where ui:attributes is used. |
| `asChild` | `boolean` | No | `-` | If true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. |
| `class` | `string` | No | `-` | The CSS class(es) to be applied to the component. |

#### Rendered data attributes

| Attribute | Description |
| --- | --- |
| `data-disabled` | Present when the field is disabled, or it is inside a disabled fieldset. |
| `data-invalid` | Present when the field is invalid: a native constraint failed, or `validate`, the form or the server reported an error. |
| `data-valid` | Present when the field has been validated and passed. |
| `data-required` | Present when the field is required. |
| `data-readonly` | Present when the field is read-only. |
| `data-touched` | Present once the user has left the field at least once. |
| `data-dirty` | Present when the value differs from the one the field started with. It goes away again when the user reverts the edit. |
| `data-filled` | Present when the field has a value. |
| `data-focus` | Present when focus is inside the field. |
| `data-type` | The state the indicator reflects. One of: "required", "invalid", "valid", "validating". |

### Machine options

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | `-` | The name of the field. It keys the field in its form and is what the field's value is read by. |
| `defaultValue` | `unknown \| undefined` | `-` | The initial value, used while the DOM holds no control for this name yet. |
| `listenTo` | `string[] \| undefined` | `-` | Names of sibling fields in the same form whose value changes this field reacts to. |
| `ids` | `ElementIds \| undefined` | `-` | The ids of the elements. Useful for composition. |
| `disabled` | `boolean \| undefined` | `false` | Whether the field is disabled. Also inherited from a surrounding native `<fieldset disabled>`. |
| `invalid` | `boolean \| undefined` | `-` | Whether the field is invalid, regardless of computed validity. Use for app-level errors (e.g. server errors) the machine cannot compute itself. |
| `dirty` | `boolean \| undefined` | `-` | Whether the field's value has been changed from its initial value. Useful when the field state is controlled by an external library. |
| `touched` | `boolean \| undefined` | `-` | Whether the field has been touched. Useful when the field state is controlled by an external library. |
| `readOnly` | `boolean \| undefined` | `false` | Whether the field is read-only. |
| `required` | `boolean \| undefined` | `false` | Whether the field is required. |
| `validate` | `((details: ValidateDetails) => ValidateResult \| Promise<ValidateResult>) \| undefined` | `-` | Custom validation, run against the value and the native validity snapshot. May return a promise; a newer validation run supersedes an in-flight one. |
| `validationMode` | `ValidationMode \| undefined` | `"onBlur"` | When validation results become visible. |
| `onValidityChange` | `((details: ValidityChangeDetails) => void) \| undefined` | `-` | Called when the committed validity changes. |
| `dir` | `"ltr" \| "rtl" \| undefined` | `"ltr"` | The document's text/writing direction. |
| `id` | `string` | `-` | The unique identifier of the machine. |
| `getRootNode` | `(() => ShadowRoot \| Document \| Node) \| undefined` | `-` | A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. |

### Machine JavaScript API

| Name | Type | Description |
| --- | --- | --- |
| `ids` | `ResolvedElementIds` | The resolved element ids, for composition with other components. |
| `disabled` | `boolean` | Whether the field is disabled (including inherited fieldset disabled state). |
| `invalid` | `boolean` | Whether the field is invalid. |
| `valid` | `boolean \| null` | Committed validity: `null` until validation has run. |
| `required` | `boolean` | Whether the field is required. |
| `readOnly` | `boolean` | Whether the field is read-only. |
| `focused` | `boolean` | Whether the control is focused. |
| `touched` | `boolean` | Whether the control has been blurred at least once. |
| `dirty` | `boolean` | Whether the value differs from the initial value. |
| `filled` | `boolean` | Whether the control has a non-empty value. |
| `validating` | `boolean` | Whether an async validation is in flight. |
| `errors` | `string[]` | The committed error messages. |
| `validity` | `ValiditySnapshot \| null` | The committed validity snapshot, `null` until validation has run. |
| `ariaDescribedby` | `string \| undefined` | The composed `aria-describedby`, for wiring custom controls. |
| `validate` | `VoidFunction` | Runs validation now, surfacing `valueMissing` even on a pristine field. |
| `clearErrors` | `VoidFunction` | Clears committed errors and returns validity to the pristine (unvalidated) state. |
| `reset` | `VoidFunction` | Resets interaction state and validity to pristine. |
| `getRootState` | `() => RootState` | Returns the state of the root. |
| `getLabelState` | `() => LabelState` | Returns the state of the label. |
| `getControlState` | `() => ControlState` | Returns the state of the control (input, textarea, select, or a custom host). |
| `getHelperTextState` | `() => HelperTextState` | Returns the state of the helper text. |
| `getErrorTextState` | `(props?: ErrorTextProps) => ErrorTextState` | Returns the state of the error text. |
| `getIndicatorState` | `(props: IndicatorProps) => IndicatorState` | Returns the state of the indicator. |
| `name` | `string` | The name of the field. |
| `getValue` | `() => FieldValue` | The current value of the field. |
| `getErrorText` | `() => string \| null` | The first error message of the field, `null` if there is none. |
| `getRootEl` | `() => HTMLElement \| null` | The root element of the field. |
| `getFormMachine` | `() => FormMachine \| undefined` | The machine of the form the field sits in, `undefined` outside of a form. |
| `setDisabled` | `(disabled: boolean) => void` | Disables or enables the field. |
| `setRequired` | `(required: boolean) => void` | Marks the field as required or optional. |
| `setReadOnly` | `(readOnly: boolean) => void` | Makes the field read-only or editable. |
| `addDependencyChangeListener` | `(callback: (detail: FieldDependencyChangeDetail) => void) => () => void` | Calls `callback` whenever the value of a field named in the `listenTo` prop of this field changes. Returns a function that removes the listener again. For a field without `listenTo` it never fires, but the returned function is still safe to call. |
