ng-error-tooltips

June 21, 2026 ยท View on GitHub

npm version build status codecov

An Angular library for Reactive Forms and Signal Forms that displays tooltips on form inputs with errors.

The latest library version is compatible with Angular 22.
Starting with version 20.1.0, ng-error-tooltips is fully zoneless-compatible.

Migration note (Signal Forms)

Starting with version 21.3.x, the ErrorTooltipSigDirective no longer reads the form field via [formField].

You must now explicitly pass the field using [errorTooltipField]:

<input
  [formField]="signalForm.name"
  ngErrorTooltipSig
  [errorTooltipField]="signalForm.name">

Demo

https://mkeller1992.github.io/ng-error-tooltips/


Install

npm i ng-error-tooltips

Setup

Standalone apps (ApplicationConfig / app.config.ts)

import { ApplicationConfig, provideZonelessChangeDetection, signal } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideErrorTooltipOptions, provideErrorTooltips, type SupportedLanguage } from 'ng-error-tooltips';
import { validate } from '@angular/forms/signals';

import { routes } from './app.routes';

// Optional: use a signal when the language can change at runtime.
export const demoLang = signal<SupportedLanguage>('de');

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideZonelessChangeDetection(),

    // lang is optional (defaults to 'de')
	// Pass either a static language string ('de', 'en' or 'fr') or a Signal<SupportedLanguage>.
    // validate is only required for Signal Forms / CustomSigValidators
    provideErrorTooltips({ lang: demoLang, validate }),

    // Optional global defaults for all tooltips.
    // Local [options] and explicit inputs like [placement] or [textColor] can still override these values.
    provideErrorTooltipOptions({
      textColor: '#7f1d1d',
      backgroundColor: '#fff7ed',
      borderColor: '#f97316',
    }),
  ],
};

Tooltip options are merged in this order:

library defaults < provideErrorTooltipOptions(...) < [options] < explicit inputs

Directives

import {
  ErrorTooltipDirective,
  ErrorTooltipSigDirective,
  ErrorTooltipSigFormDirective,
} from 'ng-error-tooltips';

Usage

Reactive Forms

Define a reactive form with validators in your TypeScript component.
For applications with language switching support, use the CustomValidatorsI18n variants.

import { Component, inject } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ErrorTooltipDirective, CustomValidators } from 'ng-error-tooltips';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
  imports: [FormsModule, ReactiveFormsModule, ErrorTooltipDirective],
})
export class AppComponent {
  private readonly formBuilder = inject(FormBuilder);

  formGroup: FormGroup = this.formBuilder.group({
    nameInput: new FormControl<string>('', {
      validators: [
        CustomValidators.required(),
        CustomValidators.minLength(3),
      ],
    }),
  });
}
<form [formGroup]="formGroup" (ngSubmit)="submit()">
  <input
    ngErrorTooltip
    formControlName="nameInput"
    placeholder="Enter your name*"
    type="text">

  <button type="submit">Submit</button>
</form>

Signal Forms

import { Component, inject, signal, viewChild } from '@angular/core';
import { form, FormField, submit } from '@angular/forms/signals';
import { CustomSigValidators, ErrorTooltipSigDirective, ErrorTooltipSigFormDirective } from 'ng-error-tooltips';

interface Employee {
  name: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  imports: [FormField, ErrorTooltipSigDirective, ErrorTooltipSigFormDirective],
})
export class AppComponent {
  private readonly v = inject(CustomSigValidators);

  readonly ttForm = viewChild(ErrorTooltipSigFormDirective);

  readonly employee = signal<Employee>({
    name: '',
  });

  readonly signalForm = form(this.employee, path => [
    this.v.requiredI18n(path.name),
    this.v.minLengthI18n(path.name, 3),
  ]);

  async submit() {
    // marks all fields touched + runs validation
    await submit(this.signalForm, async () => undefined);

    if (!this.signalForm().valid()) {
      // show all tooltips inside the container
      this.ttForm()?.showErrorTooltips();
    }
	else {
		this.ttForm()?.hideErrorTooltips();
	}
  }
}
<div ngErrorTooltipSigForm>
  <input
    [formField]="signalForm.name"
    ngErrorTooltipSig
    [errorTooltipField]="signalForm.name"    
    placeholder="Enter your name*"
    type="text">
</div>

<button type="button" (click)="submit()">Submit</button>

Three ways to pass additional properties

  1. Set global defaults for all tooltips via provideErrorTooltipOptions(...) in your ApplicationConfig, as shown above in Setup > Standalone apps:
provideErrorTooltipOptions({
  textColor: '#7f1d1d',
  backgroundColor: '#fff7ed',
  borderColor: '#f97316',
});
  1. Pass one or more properties via an ErrorTooltipOptions object:
import { ErrorTooltipOptions } from 'ng-error-tooltips';

tooltipOptions: ErrorTooltipOptions = {
  placement: 'right',
  textColor: '#7f1d1d',
  backgroundColor: '#fff7ed',
  borderColor: '#f97316',
};
<input
  formControlName="ageInput"
  ngErrorTooltip
  [options]="tooltipOptions"
  placeholder="Enter your age*"
  type="number">
  1. Pass explicit inputs directly on the control:
<input
  ngErrorTooltip
  [placement]="'right'"
  formControlName="nameInput"
  placeholder="Enter your name*"
  type="text">

Note: Tooltip options are merged as library defaults < provideErrorTooltipOptions(...) < [options] < explicit inputs.


Internationalisation (i18n)

Starting with version 21.1.0, ng-error-tooltips supports reactive multi-language error messages.

If you do nothing, the tooltip falls back to German (de) error messages.

To enable language switching, provide the current language as a Signal<'de' | 'fr' | 'en'> using provideErrorTooltips.

Whenever the language signal changes, all visible error tooltips update automatically.


Properties

nametypedefaultdescription
idstring | number0A custom id that can be assigned to the tooltip
showFirstErrorOnlybooleanfalseWhether the tooltip should only display the first error if multiple errors exist
placementPlacement'bottom-left'The position of the tooltip
zIndexnumber1101The z-index of the tooltip
tooltipClassstring''Additional CSS classes applied to the tooltip (::ng-deep)
shadowbooleantrueWhether the tooltip has a shadow
offsetnumber8Offset of the tooltip relative to the element
widthstring''Fixed width of the tooltip
maxWidthstring'350px'Maximum width of the tooltip
textColorstring''Custom text color of the tooltip
backgroundColorstring''Custom background color of the tooltip
borderColorstring''Custom border color of the tooltip and arrow
pointerEvents"auto" | "none"'auto'Whether the tooltip reacts to pointer events
appendTooltipToBodybooleantrueWhether the tooltip should be appended to the document body. Set to false for Angular Material dialogs or other overlay-based components where body-appended tooltips may appear behind the overlay

Angular Vitest unit tests

Mocking ErrorTooltipDirective (Reactive Forms)

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { ErrorTooltipDirective, MockErrorTooltipDirective } from 'ng-error-tooltips';
import { FormBuilder } from '@angular/forms';

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [AppComponent],
      providers: [FormBuilder]
    })
    .overrideComponent(AppComponent, {
      remove: { imports: [ErrorTooltipDirective] },
      add: { imports: [MockErrorTooltipDirective] }
    })
    .compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
});

Mocking ErrorTooltipSigDirective and ErrorTooltipSigFormDirective (Signal Forms)

import { ErrorTooltipSigDirective, MockErrorTooltipSigDirective } from 'ng-error-tooltips';

await TestBed.configureTestingModule({
  imports: [AppComponent],
})
  .overrideComponent(AppComponent, {
    remove: { imports: [ErrorTooltipSigDirective, ErrorTooltipSigFormDirective] },
    add: { imports: [MockErrorTooltipSigDirective, MockErrorTooltipSigFormDirective] }
  })
  .compileComponents();