ng-error-tooltips
June 21, 2026 ยท View on GitHub
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
- Set global defaults for all tooltips via
provideErrorTooltipOptions(...)in yourApplicationConfig, as shown above in Setup > Standalone apps:
provideErrorTooltipOptions({
textColor: '#7f1d1d',
backgroundColor: '#fff7ed',
borderColor: '#f97316',
});
- Pass one or more properties via an
ErrorTooltipOptionsobject:
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">
- 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
| name | type | default | description |
|---|---|---|---|
| id | string | number | 0 | A custom id that can be assigned to the tooltip |
| showFirstErrorOnly | boolean | false | Whether the tooltip should only display the first error if multiple errors exist |
| placement | Placement | 'bottom-left' | The position of the tooltip |
| zIndex | number | 1101 | The z-index of the tooltip |
| tooltipClass | string | '' | Additional CSS classes applied to the tooltip (::ng-deep) |
| shadow | boolean | true | Whether the tooltip has a shadow |
| offset | number | 8 | Offset of the tooltip relative to the element |
| width | string | '' | Fixed width of the tooltip |
| maxWidth | string | '350px' | Maximum width of the tooltip |
| textColor | string | '' | Custom text color of the tooltip |
| backgroundColor | string | '' | Custom background color of the tooltip |
| borderColor | string | '' | Custom border color of the tooltip and arrow |
| pointerEvents | "auto" | "none" | 'auto' | Whether the tooltip reacts to pointer events |
| appendTooltipToBody | boolean | true | Whether 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();