Place the Kikita control directive and the Angular Signal Forms formField binding on the same native element, then wrap it with kui-field when the UI needs a visible label, hint, error or required marker.

import { signal } from '@angular/core';
import { email, form, minLength, required } from '@angular/forms/signals';

const model = signal({
  email: '',
  project: '',
});

const profileForm = form(model, (path) => {
  required(path.email, { message: 'Email is required' });
  email(path.email, { message: 'Enter a valid email' });
  required(path.project, { message: 'Project is required' });
  minLength(path.project, 3, { message: 'Use at least 3 characters' });
});
  • A native control with [formField] projected inside kui-field lets the field infer the required marker from the Signal Forms required(...) metadata and render the first error message when one is available.
  • Use an explicit required on kui-field only when the marker should be forced without a validator. Use hideErrors when the field should keep the invalid styling without rendering automatic error text.
  • Custom Kikita form controls should prefer the Angular Signal Forms contracts FormValueControl and FormCheckboxControl, not a ControlValueAccessor, unless a compatibility layer is explicitly needed.

kui-field owns the label, hint, error, required marker and aria-describedby wiring, so do not write them by hand for normal field usage.

<kui-field label="Label" hint="Optional helper text">
  <input kuiInput [formField]="form.field" />
</kui-field>

For Signal Forms controls put range constraints in the form schema with min(...) and max(...). Do not add native min or max attributes to an element that has [formField]; Angular binds those native properties from the schema metadata.

Use projected marker directives only when the field needs custom template logic.

form.html
<kui-field>
  <label kuiLabel>Email</label>
  <input kuiInput type="email" [formField]="profileForm.email" />
  <p kuiHint>Use your work email</p>

  @if (showEmailError()) {
    <p kuiError>Email is required</p>
  }
</kui-field>

Do not use [hidden] for custom errors. Prefer Angular control flow (@if) so hidden error content is not rendered or referenced by assistive technology.

kui-segmented and kui-otp-input implement the Signal Forms control contract themselves, so formField goes on the component.

form.html
<kui-field label="View">
  <kui-segmented [formField]="myForm.view">
    <button kuiSegment value="list">List</button>
    <button kuiSegment value="grid">Grid</button>
  </kui-segmented>
</kui-field>

<kui-field label="Verification code">
  <kui-otp-input [formField]="signInForm.code" />
</kui-field>
  • Both implement FormValueControl<string>, so [formField] binds disabled, readonly, invalid, errors and touched. kui-otp-input also binds required; kui-segmented has no required state because it always shows one active segment.
  • Both implement focus(options?), so field().focusBoundControl() reaches the first focusable part (the selected segment or the first OTP cell) instead of the non-focusable group host.

How the field and its controls expose validation to assistive technology.

  • kui-field exposes its required state, from the explicit required input or a Signal Forms required(...) validator, as aria-required on Input, Textarea, Checkbox, Switch, Number Input, Color Input and the other controls that support it. Snapshot tests of the rendered markup change accordingly.
  • Signal Forms writes the raw, untouched-gated validity into the invalid of a native control. The controls therefore gate aria-invalid and the invalid styling by the touched state when [formField] is present, so a required but empty control does not turn red before the user has interacted with it. This matches the automatic error text of kui-field.
  • Outside Signal Forms the invalid input is a plain manual flag, for example for a server verdict on a one-time code.
  • A control inside kui-field takes its id, aria-describedby and invalid state from the field, so the label targets the control and the hint and error are announced.