# Form Field

> The default wrapper for a form control: label + control + hint + validation errors. It reads the projected control's `NgControl`, so it decides for itself when to show an error (touched or dirty) and which message to show. `<wr-form-item>` is the bare alternative — layout only, no knowledge of the control — and has its own page.

Source: https://ngwr.dev/reference/components/form-field  
Kind: Component, Standalone

## Installation

```angular-ts
import { WrFormField, WrFormError } from 'ngwr/form';
import { WrInput } from 'ngwr/input';   // plus whichever control you project

@Component({
  imports: [WrFormField, WrFormError, WrInput],
})
export class MyComponent {}
```

## Basic usage

Each `<wr-form-error>` is gated on the matching validator key. Errors appear once the control is touched or dirty — submit to see them all at once.

```html
<wr-form-field label="Email" hint="We'll never share it." required>
  <input wrInput [formControl]="email" type="email" />
  <wr-form-error key="required">Email is required.</wr-form-error>
  <wr-form-error key="email">That isn't a valid email.</wr-form-error>
</wr-form-field>
```

## Messages without markup

With no `<wr-form-error>` projected, the field renders the message the validator reports — resolved from the `ngwr/i18n` `validation.*` catalog, so it is already localized. Submit to see it.

```html
<!-- No <wr-form-error> at all. wr-form-field renders the message the
     validator reports, from the ngwr/i18n `validation.*` catalog. -->
<wr-form-field label="Handle" required>
  <input wrInput formControlName="handle" />
</wr-form-field>

<wr-form-field label="Age">
  <input wrInput formControlName="age" type="number" />
</wr-form-field>
```

## App-wide overrides

`provideWrFormErrors()` replaces only the keys you name. A projected `<wr-form-error key>` still wins over it — per-field copy beats app-wide copy.

```angular-ts
import { provideWrFormErrors } from 'ngwr/form';

bootstrapApplication(App, {
  providers: [
    provideWrFormErrors({
      // Only the keys you name — the rest keep resolving through the catalog.
      required: 'Please fill this in.',
      minlength: ({ error }) => `At least ${(error as { requiredLength: number }).requiredLength} characters.`,
    }),
  ],
});
```

## Optional marker

Use `optional` for the inverse — fields users can skip.

```html
<wr-form-field label="Bio" optional hint="Up to 140 characters.">
  <input wrInput [formControl]="bio" placeholder="A short tagline" />
</wr-form-field>
```

## `required` is a marker, not a validator — and it is not automatic

`<wr-form-field required>` draws the asterisk beside the label. It adds no validator, and no validator adds it: nothing on the FormControl reaches back to set it.

This is the one place the two halves of a field have to be kept in step by hand. `Validators.required` on a `FormControl` reaches an ngwr control as nothing at all — not a `required` attribute, and not `aria-required`. Angular 22's bridge does offer `required` to a control, but it lands only where the control declares an input of that name, and an ngwr control declares exactly two: `disabled` and `readonly`. So **write the validator and the marker together**, or a screen reader is told the field is optional while the form refuses to submit without it. The same is true of a signal-forms `required()` rule.

```angular-html
<!-- Both halves, every time. The validator refuses the submit;
     the marker is what the reader (and the screen reader) is told. -->
<wr-form-field label="Email" required>
  <input wrInput formControlName="email" type="email" />
</wr-form-field>

<!-- If the asterisk must follow the validator rather than be repeated, drive it
     off the control — nothing does that for you. -->
<wr-form-field label="Email" [required]="form.controls.email.hasValidator(requiredValidator)">
  <input wrInput formControlName="email" type="email" />
</wr-form-field>
```

The states that _do_ reach the control, and the ones this component renders on their behalf, are listed on [the reactive-forms guide](https://ngwr.dev/guides/forms). In short: this component reads `touched`, `dirty` and `errors` off the projected `NgControl` and owns everything it draws from them — the message, the `wr-form-field--invalid` class and the `aria-invalid` / `aria-describedby` pair — because the control itself is never told about any of them.

## Behavior notes

```html
<!-- Hint shows under the control. Hidden the moment an error becomes
     visible (the matching <wr-form-error> takes its slot). -->

<!-- Errors only render after the control is touched OR dirty,
     so the user doesn't see red on first paint. -->
```

## API

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `label` | Label text shown above the projected control. | `string` | `''` |
| `hint` | Subtext under the control. Hidden when an error is visible. | `string` | `''` |
| `required` | Show a red `*` next to the label. | `boolean` | `false` |
| `optional` | Show `(optional)` next to the label. Ignored if `required` is on. | `boolean` | `false` |
| `controlId` | Set the `<label for>` target manually. Auto-generated otherwise. | `string` | `auto` |
| `<wr-form-error>` | One message per validator key. Renders only when the control is touched / dirty and has that error. | `component` | `—` |

## See also

- [wrInput](https://ngwr.dev/reference/components/input) — The most common control to wrap. Add `wrInput` to a native `\<input>` and drop it inside.
- [wr-select](https://ngwr.dev/reference/components/select) — Pairs cleanly — the form-field surfaces select errors too.
- [wr-form-item](https://ngwr.dev/reference/components/form) — The bare alternative: layout only, `hasError` is a boolean you compute. Use it when the control is not an Angular form control.
- [WrValidators](https://ngwr.dev/reference/validators) — The error-key contract — every `WrValidators.*` member keys its error under its own name. `matchFields` is the one that lands on the GROUP, which this component does not read.
