Skip to main content

Notification Center

Global notification cards anchored to any corner of the screen. Call notify() from anywhere — no component hook needed. Cards auto-dismiss, pause on hover, and support rich HTML content.

Quick start
import { notify } from "mates-ui";

// Simple
notify("File uploaded.");

// With options
notify("Pull request merged.", { title: "Done", variant: "success" });

// Rich HTML message
notify(html`Deploy to <strong>production</strong> complete.`, {
  variant: "success",
  position: "bottom-right",
  action: { label: "View logs", onClick: openLogs },
});

// Convenience helpers
notify.success("Saved!");
notify.error("Connection lost.", { title: "Network error" });
notify.warning("Storage almost full.", { ttl: -1 });
notify.info(html`Press <kbd>Ctrl+S</kbd> to save`);

Basic — string message

import { html, renderApp } from 'mates';
import { FilledButton, OutlinedButton, notify } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-3)" wrap>
${FilledButton("Notify", { onClick: () => notify("Your changes have been saved.") })}
${OutlinedButton("With title", { onClick: () => notify("Pull request #42 was merged.", { title: "Merged" }) })}
</x-row>
`;

renderApp(App, document.getElementById('app'));

Variants

import { html, renderApp } from 'mates';
import { OutlinedButton, notify } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-2)" wrap>
${OutlinedButton("Default", { onClick: () => notify("Default notification.") })}
${OutlinedButton("Info", { onClick: () => notify.info("FYI — deployment started.") })}
${OutlinedButton("Success", { onClick: () => notify.success("File uploaded successfully.") })}
${OutlinedButton("Warning", { onClick: () => notify.warning("Storage is almost full.", { title: "Warning" }) })}
${OutlinedButton("Error", { onClick: () => notify.error("Connection lost.", { title: "Network error" }) })}
</x-row>
`;

renderApp(App, document.getElementById('app'));

Positions

import { html, renderApp } from 'mates';
import { TextButton, notify } from 'mates-ui';

const POSITIONS = ["top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right"];

const App = () => () => html`
<x-row gap="var(--md-space-2)" wrap>
${POSITIONS.map((pos) => TextButton(pos, { size: "sm", onClick: () => notify("Position: " + pos, { position: pos }) }))}
</x-row>
`;

renderApp(App, document.getElementById('app'));

Action button

import { html, renderApp } from 'mates';
import { FilledButton, OutlinedButton, notify } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-3)" wrap>
${FilledButton("With action", { onClick: () => notify("3 files deleted.", { title: "Deleted", action: { label: "Undo", onClick: () => notify.success("Restored!", { ttl: 3000 }) } }) })}
${OutlinedButton("Open PR", { onClick: () => notify.info("Review requested on PR #87.", { title: "New review", action: { label: "Open", onClick: () => {} } }) })}
</x-row>
`;

renderApp(App, document.getElementById('app'));

Rich HTML content

import { html, renderApp } from 'mates';
import { FilledButton, OutlinedButton, notify } from 'mates-ui';

const App = () => () => html`
<x-row gap="var(--md-space-3)" wrap>
${FilledButton("HTML message", { onClick: () => notify(html`Deploy to <strong>production</strong> complete in <em>1m 42s</em>.`, { variant: "success", title: "Deployed" }) })}
${OutlinedButton("Shortcut tip", { onClick: () => notify.info(html`Press <kbd style="background:var(--md-color-border);padding:0.1rem 0.3rem;border-radius:0.2rem;">Ctrl+S</kbd> to save.`) })}
</x-row>
`;

renderApp(App, document.getElementById('app'));

Persistent card + programmatic dismiss

import { html, atom, renderApp } from 'mates';
import { FilledButton, OutlinedButton, TextButton, notify } from 'mates-ui';

const App = () => {
const pid = atom(null);
return () => html`
<x-row gap="var(--md-space-3)" wrap>
${FilledButton("Show persistent", {
disabled: pid() !== null,
onClick: () => {
const id = notify.warning("This card won't auto-dismiss.", { title: "Persistent", ttl: -1, id: "demo-persistent", onDismiss: () => pid.set(null) });
pid.set(id);
},
})}
${OutlinedButton("Dismiss it", { disabled: pid() === null, onClick: () => { if (pid()) notify.dismiss(pid()); } })}
${TextButton("Dismiss all", { onClick: () => notify.dismissAll() })}
</x-row>
`;
};

renderApp(App, document.getElementById('app'));

onDismiss callback

import { html, atom, renderApp } from 'mates';
import { FilledButton, Text, notify } from 'mates-ui';

const App = () => {
const log = atom([]);
return () => html`
<x-col gap="var(--md-space-3)">
${FilledButton("Show + track", {
onClick: () => {
const ts = new Date().toLocaleTimeString();
notify("Dismiss me any way you like.", { title: "Tracked", onDismiss: () => log.set(["Dismissed at " + ts, ...log()].slice(0, 5)) });
},
})}
${log().length === 0
? Text("No events yet", { type: "body-sm", color: "muted" })
: html`<x-col gap="var(--md-space-1)">${log().map((l) => Text(l, { type: "body-sm", color: "muted" }))}</x-col>`}
</x-col>
`;
};

renderApp(App, document.getElementById('app'));

notify

import { notify } from "mates-ui";
Prop Type Default Description
notify.dismiss(id) void Dismiss a specific card by id (plays leave animation).
notify.dismissAll(position?) void Dismiss all cards — optionally scoped to a single position.
notify.error(message, options?) string Shorthand — red left-border accent.
notify.info(message, options?) string Shorthand — blue left-border accent.
notify.success(message, options?) string Shorthand — green left-border accent.
notify.warning(message, options?) string Shorthand — amber left-border accent.
notify(message, options?) string Show a notification card from anywhere. Accepts a plain string or a lit-html TemplateResult. Returns the card id.

NotifOptions (second argument)

Prop Type Default Description
action { label: string; onClick: () => void } Action button below the message. Clicking fires onClick then dismisses.
dismissible boolean true Show the × close button.
id string Deduplication key — replaces an existing card with the same id.
onClick () => void Called when the user clicks the card body.
onDismiss () => void Called when the card is dismissed for any reason.
position "top-right" | "top-left" | "top-center" | "bottom-right" | "bottom-left" | "bottom-center" "top-right" Screen corner for this notification.
title string Bold heading above the message.
ttl number 6000 Auto-dismiss delay in ms. Pass -1 for a persistent card.
variant "default" | "info" | "success" | "warning" | "error" "default" Coloured left-border accent.

Caveats

  • `notify` is a global singleton — no component context or hook needed.
  • Up to 6 independent containers exist (one per position), lazily created on first use.
  • Hovering a card pauses the auto-dismiss timer and resumes from where it left off.
  • Maximum 5 cards per position. The oldest is evicted when the limit is reached.