TeqBench
Getting started

Show your first banner


3 min readupdated April 2026

Banners are the heavier-duty message surface in the @teqbench family — wider than a notification, can hold multi-line text, optional action buttons and form controls (checkboxes, toggles, radio groups), and dismisses only on an explicit user action or a programmatic call. The same package ships two ways to render one: an overlay produced by TbxMatBannerService (pinned to the viewport top or bottom via CDK Overlay) and an inline <tbx-mat-banner> component you embed directly in a template.

This guide assumes you already followed Install & authenticate and Configure the severity theme. The banner package peer-depends on the severity theme and picks up the same six-tier color palette.

Install the package

npm install @teqbench/tbx-mat-banners

Provide the banner config at bootstrap

Banners need a severity-icon resolver to draw the leading tier icon. The package ships two: TbxMatBannerSeverityFontIconService (Material Symbols ligatures) and TbxMatBannerSeveritySvgIconService (inline SVGs). Wire one via the TBX_MAT_BANNER_PROVIDER_CONFIG injection token in app.config.ts:

import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideTbxMatSeverityTheme } from '@teqbench/tbx-mat-severity-theme';
import {
  TBX_MAT_BANNER_PROVIDER_CONFIG,
  TbxMatBannerAnimation,
  TbxMatBannerSeverityFontIconService,
} from '@teqbench/tbx-mat-banners';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideAnimationsAsync(),
    provideTbxMatSeverityTheme({ invert: false }),
    {
      provide: TBX_MAT_BANNER_PROVIDER_CONFIG,
      useFactory: () => ({
        severityIconResolverService: new TbxMatBannerSeverityFontIconService(
          'material-symbols-rounded',
        ),
        defaultAnimation: TbxMatBannerAnimation.Slide,
      }),
    },
  ],
});

defaultAnimation is optional. When unset, banners default to TbxMatBannerAnimation.None (no transition). Per-banner animation still overrides whatever the provider picks.

Fire an overlay banner

Inject TbxMatBannerService and call a severity method:

import { Component, inject } from '@angular/core';
import { TbxMatBannerService } from '@teqbench/tbx-mat-banners';

@Component({
  selector: 'app-upload-button',
  template: `<button (click)="onFailed()">Simulate failure</button>`,
})
export class UploadButtonComponent {
  private readonly banner = inject(TbxMatBannerService);

  onFailed(): void {
    void this.banner.error('Upload failed. Check your connection and retry.');
  }
}

The convenience methods (success / error / warning / information / help) each take a message string and an optional config. The call returns a TbxMatBannerRef synchronously — a leading void discards it when you don't need the handle. By default, an overlay banner pins to the viewport top, auto-dismiss is disabled (duration: 0), and both the severity icon and the close button are visible.

Customize a single call

The most common per-call overrides:

this.banner.warning('Connection unstable.', {
  verticalPosition: 'bottom',
  animation: TbxMatBannerAnimation.Fade,
  duration: 6000,
});

this.banner.information('Release notes ready.', {
  showSeverityIcon: false,
});

duration works the same as notifications — <= 0 (or omitted) means indefinite; > 0 auto-dismisses after that many ms. verticalPosition is 'top' or 'bottom'. animation accepts TbxMatBannerAnimation.None | .Slide | .Fade. Animations respect the user's prefers-reduced-motion setting automatically.

Add an actions group

Every call can include an actionsGroup — an ordered array of buttons and form controls rendered between the message and the close button. The simplest case is a single button:

this.banner.success('Item deleted.', {
  duration: 30_000,
  actionsGroup: [
    { type: 'button', key: 'undo', label: 'Undo', appearance: 'tonal' },
  ],
});

Clicking a button dismisses the banner and resolves the ref's result promise with dismissReason: Action and actionKey: 'undo'.

Buttons and form controls mix freely. An update prompt with an "auto-update" toggle:

this.banner.information('An update is available.', {
  actionsGroup: [
    { type: 'toggle', key: 'autoUpdate', label: 'Auto-update from now on' },
    { type: 'button', key: 'later', label: 'Later' },
    { type: 'button', key: 'update', label: 'Update now', appearance: 'filled' },
  ],
});

Form controls (checkbox, toggle, radio-group, toggle-group) don't dismiss — only buttons do. When a button dismisses the banner, every control's current value is snapshotted into result.actionsGroupValues, keyed by its key:

const ref = this.banner.information('An update is available.', {
  actionsGroup: [
    { type: 'toggle', key: 'autoUpdate', label: 'Auto-update' },
    { type: 'button', key: 'update', label: 'Update now' },
  ],
});

const result = await ref.result;
if (result.actionKey === 'update') {
  const autoUpdate = result.actionsGroupValues['autoUpdate'] as boolean;
  await this.applyUpdate({ schedule: autoUpdate });
}

Checkbox and toggle values are boolean. Radio-group and single-select toggle-group values are string | undefined. Multi-select toggle-group values are string[].

React to dismissal

Same pattern as notifications — await ref.result and switch on the reason:

import {
  TbxMatBannerDismissReason,
  TbxMatBannerService,
} from '@teqbench/tbx-mat-banners';

async onDelete(id: string): Promise<void> {
  const ref = this.banner.success('Item deleted.', {
    duration: 30_000,
    actionsGroup: [
      { type: 'button', key: 'undo', label: 'Undo' },
    ],
  });

  const result = await ref.result;
  switch (result.dismissReason) {
    case TbxMatBannerDismissReason.Action:
      if (result.actionKey === 'undo') await this.restore(id);
      break;
    case TbxMatBannerDismissReason.Close:
    case TbxMatBannerDismissReason.Timeout:
      // User let it expire — nothing to do.
      break;
  }
}

The full reason enum (Action, Close, Timeout, ProgrammaticDismissAll, ProgrammaticDismissCurrent) mirrors the notification API.

Dismiss programmatically

this.banner.dismiss();      // close the active banner (queued ones stay)
this.banner.dismissAll();   // clear queue + close active

Queued banners cleared by dismissAll() resolve their ref.result with ProgrammaticDismissAll without ever having rendered.

Render a banner inline

For a banner that sits inside a specific component's layout (rather than floating over the viewport), use the <tbx-mat-banner> component directly. It takes signal inputs for every config field and emits a dismissed event carrying the same TbxMatBannerResult:

import { Component, inject } from '@angular/core';
import {
  TbxMatBannerComponent,
  TbxMatBannerDismissReason,
  TbxMatBannerResult,
} from '@teqbench/tbx-mat-banners';
import { TbxMatSeverityLevel } from '@teqbench/tbx-mat-severity-theme';

@Component({
  selector: 'app-offline-notice',
  imports: [TbxMatBannerComponent],
  template: `
    <tbx-mat-banner
      [type]="severity"
      [message]="message"
      [actionsGroup]="actions"
      (dismissed)="onDismiss($event)"
    />
  `,
})
export class OfflineNoticeComponent {
  readonly severity = TbxMatSeverityLevel.Warning;
  readonly message = 'You are currently offline. Some data may be out of date.';
  readonly actions = [
    { type: 'button' as const, key: 'retry', label: 'Retry', appearance: 'tonal' as const },
  ];

  onDismiss(result: TbxMatBannerResult): void {
    if (result.dismissReason === TbxMatBannerDismissReason.Action && result.actionKey === 'retry') {
      this.retryConnection();
    }
  }

  private retryConnection(): void {
    /* … */
  }
}

The inline component carries the severity panel class on its host automatically, so the severity-theme tokens apply without any extra styling on your side. verticalPosition, animation, and panelClass are overlay-only fields — ignored in inline mode because there's no overlay frame around the component.

What to try next