Skip to content

Show non-blocking toast notifications for save confirmations, errors, and other transient feedback. Toasts are anchored to the Handsontable root, support stacking per corner, and work with the design system themes.

Overview

The Notification plugin adds a small API on top of the grid: showMessage, hide, hideAll, isVisible, and getQueueSize. You can choose severity variants, corner positions, optional titles, action buttons, and auto-dismiss timing. The plugin is disabled by default; set notification to true or to a configuration object.

Toasts use aria-live="polite" for informational variants and aria-live="assertive" for the error variant so assistive technologies announce them without moving keyboard focus. Press F6 to move focus into the notification region when at least one toast is visible; Tab and Shift+Tab then move between controls across visible toasts. Tab from the last control in the region moves focus to the highlighted grid cell (the notification host follows the grid in the DOM, so default tab order would otherwise leave the table). Shift+Tab from the first control behaves like Escape and returns focus to the element that was active before you entered. Press Escape to leave the region the same way. Hiding the last toast while focus is in the notification region or on that toast restores focus the same way as Escape. Opening a toast does not move focus on its own.

Basic configuration

Enable the plugin with notification: true, then call showMessage() for each toast. The example below opens four toasts at once (duration: 0), one in each corner (top-start, top-end, bottom-start, and bottom-end), with info, success, warning, and error variants.

TypeScript
import { Component, AfterViewInit, ViewChild } from '@angular/core';
import Handsontable from 'handsontable/base';
import { HotTableComponent } from '@handsontable/angular-wrapper';
import { registerAllModules } from 'handsontable/registry';
registerAllModules();
@Component({
selector: 'notification-example',
templateUrl: './example1.html',
})
export class NotificationExampleComponent implements AfterViewInit {
@ViewChild('hotTable', { static: false }) hotTable: HotTableComponent;
hotSettings: Handsontable.GridSettings = {
data: [
['Review pricing sheet', 'Draft', 'Apr 12', 'Morgan Lee', 'Medium', 'Finance'],
['Ship partner samples', 'In progress', 'Apr 14', 'Jordan Kim', 'High', 'Sales'],
['Q2 revenue forecast', 'Done', 'Apr 8', 'Avery Chen', 'Low', 'Finance'],
['New client onboarding', 'Blocked', 'Apr 18', 'Riley Patel', 'High', 'Success'],
['Update documentation', 'Draft', 'Apr 20', 'Casey Ruiz', 'Low', 'Engineering'],
['Audit vendor contracts', 'In progress', 'Apr 22', 'Morgan Lee', 'Medium', 'Legal'],
['Refresh brand assets', 'Draft', 'Apr 25', 'Sam Okafor', 'Low', 'Marketing'],
['Fix login timeout bug', 'In progress', 'Apr 16', 'Devon Walsh', 'High', 'Engineering'],
['Prep board deck', 'Done', 'Apr 10', 'Jordan Kim', 'Medium', 'Exec'],
['Migrate legacy CRM rows', 'Blocked', 'Apr 28', 'Alex Rivera', 'High', 'Engineering'],
['Schedule user interviews', 'Draft', 'Apr 19', 'Riley Patel', 'Medium', 'Product'],
['Approve expense policy', 'In progress', 'Apr 21', 'Morgan Lee', 'Low', 'Finance'],
['Localize help center', 'Draft', 'May 2', 'Sam Okafor', 'Medium', 'Marketing'],
['Load test checkout API', 'In progress', 'Apr 17', 'Devon Walsh', 'High', 'Engineering'],
['Quarterly OKR check-in', 'Done', 'Apr 9', 'Avery Chen', 'Low', 'Exec'],
],
colHeaders: ['Task', 'Status', 'Due', 'Owner', 'Priority', 'Team'],
columns: [
{ data: 0, type: 'text', width: 200 },
{ data: 1, type: 'text', width: 105 },
{ data: 2, type: 'text', width: 72 },
{ data: 3, type: 'text', width: 120 },
{ data: 4, type: 'text', width: 80 },
{ data: 5, type: 'text', width: 100 },
],
rowHeaders: true,
width: '100%',
height: 420,
notification: true,
licenseKey: 'non-commercial-and-evaluation',
};
ngAfterViewInit(): void {
const hot = this.hotTable?.hotInstance;
if (!hot) {
return;
}
const notification = hot.getPlugin('notification');
notification.showMessage({
title: 'Top start',
message: 'Info toast in the top-start corner.',
variant: 'info',
position: 'top-start',
duration: 0,
});
notification.showMessage({
title: 'Top end',
message: 'Success toast in the top-end corner.',
variant: 'success',
position: 'top-end',
duration: 0,
});
notification.showMessage({
title: 'Bottom start',
message: 'Warning toast in the bottom-start corner.',
variant: 'warning',
position: 'bottom-start',
duration: 0,
});
notification.showMessage({
title: 'Bottom end',
message: 'Error toast in the bottom-end corner.',
variant: 'error',
position: 'bottom-end',
duration: 0,
});
}
}
HTML
<hot-table #hotTable [settings]="hotSettings"></hot-table>

Toolbar actions (inventory-style)

Use separate buttons for save, error recovery, and warnings. Primary and secondary actions on an error toast can call hideAll() before showing follow-up feedback.

TypeScript
import { Component, ViewChild } from '@angular/core';
import Handsontable from 'handsontable/base';
import { HotTableComponent } from '@handsontable/angular-wrapper';
import { registerAllModules } from 'handsontable/registry';
registerAllModules();
@Component({
selector: 'notification-example-2',
templateUrl: './example2.html',
})
export class NotificationExample2Component {
@ViewChild('hotTable', { static: false }) hotTable: HotTableComponent;
hotSettings: Handsontable.GridSettings = {
data: [
['SKU-001', 'Alkaline AA 4pk', 240, 40, 'A-12'],
['SKU-002', 'USB-C cable 1m', 18, 24, 'B-03'],
['SKU-003', 'Notebook A5 ruled', 0, 30, 'C-01'],
['SKU-004', 'Wireless mouse', 6, 15, 'B-07'],
['SKU-005', 'HDMI cable 2m', 2, 10, 'A-04'],
['SKU-006', 'Desk lamp LED', 45, 12, 'D-02'],
['SKU-007', 'Laptop stand aluminum', 0, 8, 'C-14'],
['SKU-008', 'Mechanical keycap set', 112, 20, 'B-01'],
],
colHeaders: ['SKU', 'Product', 'Qty on hand', 'Reorder at', 'Bin'],
columns: [
{ data: 0, type: 'text', width: 90 },
{ data: 1, type: 'text', width: 200 },
{ data: 2, type: 'numeric', width: 100 },
{ data: 3, type: 'numeric', width: 95 },
{ data: 4, type: 'text', width: 70 },
],
rowHeaders: true,
width: '100%',
height: 280,
notification: true,
licenseKey: 'non-commercial-and-evaluation',
};
onSave(): void {
const hot = this.hotTable?.hotInstance;
if (!hot) {
return;
}
hot.getPlugin('notification').showMessage({
title: 'Saved',
message: 'Inventory updates were written.',
variant: 'success',
position: 'top-end',
duration: 2500,
});
}
onSyncError(): void {
const hot = this.hotTable?.hotInstance;
if (!hot) {
return;
}
const plugin = hot.getPlugin('notification');
plugin.showMessage({
title: 'Sync failed',
message: 'The service is unavailable. Retry when your connection is stable.',
variant: 'error',
position: 'bottom-end',
duration: 0,
actions: [
{
label: 'Retry',
type: 'primary',
callback: () => {
plugin.hideAll();
plugin.showMessage({
message: 'Sync completed.',
variant: 'success',
position: 'bottom-end',
});
},
},
{ label: 'Dismiss', type: 'secondary', callback: () => plugin.hideAll() },
],
});
}
onLowStock(): void {
const hot = this.hotTable?.hotInstance;
if (!hot) {
return;
}
hot.getPlugin('notification').showMessage({
title: 'Review quantities',
message:
'SKUs below reorder: USB-C cable 1m, Wireless mouse, HDMI cable 2m. Out of stock: Notebook A5 ruled, Laptop stand.',
variant: 'warning',
position: 'top-start',
duration: 6000,
});
}
}
HTML
<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap;">
<button type="button" (click)="onSave()">Save</button>
<button type="button" (click)="onSyncError()">Sync error</button>
<button type="button" (click)="onLowStock()">Low stock</button>
</div>
<hot-table #hotTable [settings]="hotSettings"></hot-table>

API summary

MethodDescription
showMessage(options)Shows a toast. Returns a string id.
hide(id)Hides one toast.
hideAll()Hides every visible toast and clears the queue.
isVisible(id?)Returns whether a given toast or any toast is visible.
getQueueSize()Returns how many toasts are waiting when stackLimit is reached.

Hooks: beforeNotificationShow, afterNotificationShow, beforeNotificationHide, and afterNotificationHide. Returning false from beforeNotificationShow cancels showMessage (no id, nothing enqueued). That hook runs once per call to showMessage, including when the toast is queued because stackLimit is full; it does not run again when a queued toast mounts. Returning false from beforeNotificationHide keeps the toast visible and stops automatic dismissal for that toast.

Read more