Toolbar
Beta

Field

Composable form field container for native controls. The field owns the visual shell, label, floating outline, prefix/suffix slots, hint, required marker, disabled state, and error slot while the actual value stays on a native control directive such as input[fktInputText].

Import

import {FktFieldComponent} from "frakton-ng/field";

Composition

Field composition patterns. A fkt-field wraps a native control directive such as input[fktInputText]. The field owns the visual shell while the projected control owns value, native attributes, and form integration.

Basic input

Basic text input. The field renders the label and outline while input[fktInputText] remains the real form control, so native attributes and browser behavior stay available to the consumer.


    

Sizes

Field sizes provide predefined densities for compact filters, default forms, and larger touch targets. The size belongs to the field shell so future controls keep the same visual rhythm.


    



    



    

Prefix suffix

Prefix and suffix slots are projected with [fktFieldPrefix] and [fktFieldSuffix]. The field owns spacing around the slots, while the projected content can be any component or directive such as icons, buttons, or tooltips.


    
    
    

Hint

Hints provide secondary guidance below the field. Use the hint input for plain text, project [fktHintStart] when the message needs custom template content, or [fktHintEnd] for right-aligned supporting metadata. Visible errors replace the start hint.


    



    

    
        Your workspace will be available at acme.frakton.app.
    

Character count

Character count is opt-in through fktCharacterCount on a textual control. The field renders the computed count in the hint end slot by default. Project [fktHintEnd] to replace that default with custom supporting metadata.


    



    

    
        Custom meta
    

Required marker

Required marker behavior. When requiredMarker is omitted, the field tries to infer the marker from the projected control validation state. Pass [requiredMarker]="true" to force it on, or [requiredMarker]="false" to hide it while keeping the validation rule active.


    



    



    

Hidden label

Hidden labels keep compact controls visually clean without dropping the accessible label. Use this for search boxes, table filters, command inputs, and other cases where the placeholder is visible but the control still needs a stable semantic name.


    
    

Forms

Form integration examples. The field reads state from the projected FktFieldControl; the consumer keeps validation rules in the form model instead of passing disabled, touched, invalid, or required flags into the field manually.

Signal forms

Signal Forms integration with Angular's [field] directive. The field reacts to value, touched, invalid, disabled, required, and error state exposed by the projected control.


    
    



    
    



    
    



    
    

Reactive forms

Reactive Forms integration through formControlName. Programmatic updates, reset, disabled state, and validation changes are read from the Angular control adapter instead of relying only on DOM attributes.

Error handling

Error handling examples. By default, the field shows its error state when the projected control is invalid and touched. That default can be overridden for submit attempts, server-side validation, or custom form flows.

Manual error control

Manual error visibility through showError. When the input is defined, it replaces the default invalid && touched rule for both the red border and the message area.


    
    



Custom error

Custom projected error content. When [fktError] content is provided, it replaces the automatic error message while still using the field's visibility and spacing behavior.


    
    

    
        Use a company e-mail address.
    

Automatic error messages

Automatic error messages with provideFktConfig(withFieldErrorMessages(...)). The resolver turns normalized Signal Forms or Reactive Forms errors into the text rendered by each field.

export const appConfig: ApplicationConfig = {
    providers: [
        provideFktConfig(
            withFieldErrorMessages(({errors}) => {
                if (!errors) return null;
                const first = errors.errors[0];

                if (first.message) return first.message; // If field already has a message defined, returns it

                if (first.kind === 'required') return 'Field is required';
                if (first.kind === 'email') return 'Enter a valid email address';

                if (first.kind === 'minLength') {
                    return `Use at least ${first.params['minLength']} characters`;
                }

                if (first.kind === 'maxLength') {
                    return `Use at most ${first.params['maxLength']} characters`;
                }

                return null;
            })
        )
    ]
}

    
    

Translated error messages

Translated error messages combine withI18nIntegration(...) and withFieldErrorMessages(...). The field recomputes visible errors when the configured language source changes. recomputeOn accepts a Signal, an Observable, or an array mixing Signals and Observables.

export const appConfig: ApplicationConfig = {
    providers: [
        provideFktConfig(
            withI18nIntegration(() => {
                const translateService = inject(TranslateService);

                return {
                    recomputeOn: translateService.currentLanguage$,
                    translateFn: translateService.instant.bind(translateService),
                };
            }),
            withFieldErrorMessages(({errors, t}) => {
                if (!errors) return null;

                const first = errors.errors[0];
                if (first.message) return first.message;

                if (first.kind === 'required') return t('errors.required');
                if (first.kind === 'email') return t('errors.email');

                if (first.kind === 'minLength') {
                    return t('errors.minLength', first.params);
                }

                if (first.kind === 'maxLength') {
                    return t('errors.maxLength', first.params);
                }

                return null;
            })
        )
    ]
}
withI18nIntegration(() => ({
  recomputeOn: currentLanguage, // Signal
  translateFn: translate,
}))

withI18nIntegration(() => ({
  recomputeOn: translateService.onLangChange, // Observable
  translateFn: translateService.instant.bind(translateService),
}))

withI18nIntegration(() => ({
  recomputeOn: [currentLanguage, translateService.onLangChange], // Signal + Observable
  translateFn: translateService.instant.bind(translateService),
}))



    
    



    
    



    
    



    
    

Field

Configuration Options

Required Marker

requiredMarker controls only the visual marker rendered next to the label. Validation still belongs to the projected control or form model.

When requiredMarker is not defined, the field tries to infer the marker from the projected control:

  • Signal Forms: reads the field required state.
  • Reactive Forms: runs the configured validator and checks for a required error.
  • Native inputs: reads the native required property.

Use [requiredMarker]="true" to force the marker on and [requiredMarker]="false" to hide it.

Field Error Messages

Global error messages are configured with provideFktConfig and withFieldErrorMessages.

provideFktConfig(
  withFieldErrorMessages(({ errors, t }) => {
    if (!errors) return null;

    const first = errors.errors[0];
    if (first.message) return first.message;

    if (first.kind === 'required') return t('errors.required');
    if (first.kind === 'email') return t('errors.email');

    if (first.kind === 'minLength' || first.kind === 'minlength') {
      return t('errors.minLength', first.params);
    }

    if (first.kind === 'maxLength' || first.kind === 'maxlength') {
      return t('errors.maxLength', first.params);
    }

    return null;
  })
)

I18n Integration

withI18nIntegration connects Frakton NG to the application's translation service. The library does not own translation files or formats; it only receives a translateFn and a source that triggers recomputation.

recomputeOn accepts:

  • a Signal;
  • an Observable;
  • an array mixing Signals and Observables.
provideFktConfig(
  withI18nIntegration(() => {
    const translateService = inject(TranslateService);

    return {
      recomputeOn: translateService.currentLanguage$,
      translateFn: translateService.instant.bind(translateService),
    };
  }),
  withFieldErrorMessages(({ errors, t }) => {
    if (!errors) return null;

    const first = errors.errors[0];
    if (first.message) return first.message;

    if (first.kind === 'required') return t('errors.required');
    if (first.kind === 'email') return t('errors.email');

    if (first.kind === 'minLength' || first.kind === 'minlength') {
      return t('errors.minLength', first.params);
    }

    if (first.kind === 'maxLength' || first.kind === 'maxlength') {
      return t('errors.maxLength', first.params);
    }

    return null;
  })
)
withI18nIntegration(() => ({
  recomputeOn: currentLanguage, // Signal
  translateFn: translate,
}))

withI18nIntegration(() => ({
  recomputeOn: translateService.onLangChange, // Observable
  translateFn: translateService.instant.bind(translateService),
}))

withI18nIntegration(() => ({
  recomputeOn: [currentLanguage, translateService.onLangChange],
  translateFn: translateService.instant.bind(translateService),
}))