Skip to main content

Snackbars & Toasts

Lightweight notifications with auto-dismiss, actions, and five severity variants. Call useSnackbar() in your component outer function to get the snack helper, or use the static Snackbar component for render-controlled surfaces.

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

const MyComponent = () => {
  const snack = useSnackbar();

  return () => {
    // One-liner helpers — appended to <body>, auto-dismiss after 6 s
    snack.success("Profile saved");
    snack.error("Upload failed", { ttl: -1 });
    snack.warning("Storage almost full", {
  action: { label: "Manage", onClick: openStorage },
});
snack.info("New version available");
snack.default("Copied to clipboard");

// Position control (six pre-created stacks)
snack.info("Top right", { position: "top-right" });
snack.success("Bottom left", { position: "bottom-left" });

// Programmatic dismiss
const id = snack.info("Syncing…", { ttl: -1, dismissible: false });
await doSync();
snack.dismiss(id);

Variants

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

const App = () => {
const snack = useSnackbar();
return () => html`
<x-row gap="var(--md-space-2)" wrap>
${OutlinedButton("Default", { onClick: () => snack.default("Copied to clipboard") })}
${FilledButton("Success", { onClick: () => snack.success("Changes saved successfully") })}
${DangerButton("Error", { onClick: () => snack.error("Something went wrong. Please try again.") })}
${OutlinedButton("Warning", { onClick: () => snack.warning("You are running low on storage space.") })}
${OutlinedButton("Info", { onClick: () => snack.info("A new version is available.") })}
</x-row>
`;
};

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

Positions

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

const App = () => {
const snack = useSnackbar();
return () => html`
<x-row gap="var(--md-space-2)" wrap>
${OutlinedButton("Top left", { size: "sm", onClick: () => snack.info("Top left", { position: "top-left", ttl: 2500 }) })}
${OutlinedButton("Top center", { size: "sm", onClick: () => snack.info("Top center", { position: "top-center", ttl: 2500 }) })}
${OutlinedButton("Top right", { size: "sm", onClick: () => snack.info("Top right", { position: "top-right", ttl: 2500 }) })}
${FilledButton("Bottom center", { size: "sm", onClick: () => snack.success("Bottom center", { position: "bottom-center", ttl: 2500 }) })}
${OutlinedButton("Bottom left", { size: "sm", onClick: () => snack.success("Bottom left", { position: "bottom-left", ttl: 2500 }) })}
${OutlinedButton("Bottom right", { size: "sm", onClick: () => snack.success("Bottom right", { position: "bottom-right", ttl: 2500 }) })}
</x-row>
`;
};

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

With action button

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

const App = () => {
const snack = useSnackbar();
return () => html`
<x-row gap="var(--md-space-2)" wrap>
${OutlinedButton("Undo action", { onClick: () => snack.default("Item deleted", { action: { label: "Undo", onClick: () => snack.success("Undo successful") } }) })}
${DangerButton("Retry on error", { onClick: () => snack.error("Upload failed", { action: { label: "Retry", onClick: () => snack.info("Retrying…") }, ttl: -1 }) })}
${OutlinedButton("Manage storage", { onClick: () => snack.warning("Storage almost full", { action: { label: "Manage", onClick: () => snack.info("Opening storage…") } }) })}
</x-row>
`;
};

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

Persistent (ttl: -1)

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

const App = () => {
const snack = useSnackbar();
return () => html`
<x-row gap="var(--md-space-2)" wrap>
${OutlinedButton("Start sync", {
onClick: () => {
snack.info("Syncing data…", { ttl: -1, dismissible: false, action: { label: "Cancel", onClick: () => {} } });
setTimeout(() => snack.success("Sync complete"), 3000);
},
})}
${DangerButton("Persistent error", {
onClick: () => snack.error("Connection lost — working offline", { ttl: -1, action: { label: "Dismiss", onClick: () => {} } }),
})}
</x-row>
`;
};

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

Without dismiss button

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

const App = () => {
const snack = useSnackbar();
return () => html`
<x-row>
${OutlinedButton("No dismiss (3 s)", {
onClick: () => snack.success("Auto-dismisses in 3 seconds", { dismissible: false, ttl: 3000 }),
})}
</x-row>
`;
};

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

Stacking multiple

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

const App = () => {
const snack = useSnackbar();
return () => html`
<x-row>
${FilledButton("Fire 3 toasts", {
onClick: () => {
snack.success("File uploaded");
snack.info("Processing started");
snack.warning("Low memory");
},
})}
</x-row>
`;
};

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

Static Snackbar component

import { html, atom, nothing, renderApp } from 'mates';
import { Snackbar, FilledButton, DangerButton, OutlinedButton, useSnackbar } from 'mates-ui';

const App = () => {
const snack = useSnackbar();
const open = atom(false);
const openSucc = atom(false);
const openErr = atom(false);
const openAct = atom(false);
return () => html`
<x-col gap="var(--md-space-4)">
<x-row gap="var(--md-space-2)" wrap>
${OutlinedButton("Default", { onClick: () => open.set(!open()) })}
${FilledButton("Success", { onClick: () => openSucc.set(!openSucc()) })}
${DangerButton("Error", { onClick: () => openErr.set(!openErr()) })}
${OutlinedButton("With action", { onClick: () => openAct.set(!openAct()) })}
</x-row>
${open() ? Snackbar({ message: "This is a default static snackbar.", position: "bottom-end", dismissible: true, onDismiss: () => open.set(false) }) : nothing}
${openSucc() ? Snackbar({ message: "Profile updated successfully.", variant: "success", position: "bottom-end", dismissible: true, onDismiss: () => openSucc.set(false) }) : nothing}
${openErr() ? Snackbar({ message: "Failed to save changes.", variant: "error", position: "bottom-end", dismissible: true, onDismiss: () => openErr.set(false) }) : nothing}
${openAct() ? Snackbar({ message: "Comment deleted.", position: "bottom-end", dismissible: true, action: { label: "Undo", onClick: () => { openAct.set(false); snack.success("Comment restored"); } }, onDismiss: () => openAct.set(false) }) : nothing}
</x-col>
`;
};

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

useSnackbar()

import { useSnackbar, Snackbar } from "mates-ui";
Prop Type Default Description
snack.default(msg, opts?) string Neutral inverse-surface snackbar — generic notification.
snack.error(msg, opts?) string Red snackbar — signals a failure or destructive event.
snack.info(msg, opts?) string Neutral inverse-surface snackbar — contextual information.
snack.success(msg, opts?) string Green snackbar — confirms a completed action.
snack.warning(msg, opts?) string Amber snackbar — draws attention to a potential problem.

SnackbarOptions (second argument to toast helpers)

Prop Type Default Description
action { label: string; onClick: () => void } Optional action button rendered inside the snackbar. Clicking triggers the leave animation.
dismissible boolean true Whether to render the × dismiss button.
dismissLabel string "Dismiss" Dismiss button aria-label.
id string Deduplication key — if an entry with this id already exists, the existing snackbar is replaced (timers reset).
position "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center" "bottom-center" Target snackbar stack position. Mates-UI pre-creates six fixed containers (one per position).
ttl number 6000 Auto-dismiss delay in ms. Pass -1 for a persistent snackbar that never auto-dismisses.

Static Snackbar component

Prop Type Default Description
action { label: string; onClick: () => void } Optional action button.
dismissible boolean false Show dismiss × button.
message* string | TemplateResult Main message content.
onDismiss () => void Called when the dismiss button is clicked.
position "bottom-center" | "bottom-start" | "bottom-end" | "top-center" | "top-start" | "top-end" "bottom-center" Fixed position on screen.
variant "default" | "success" | "error" | "warning" "default" Visual style variant.

Caveats

  • Call useSnackbar() in the component outer function. SnackbarScope creates six fixed containers per component tree automatically.
  • snack.add() returns the entry id so you can dismiss programmatically with snack.dismiss(id).
  • Pass ttl: -1 for persistent snackbars (e.g. background sync in progress) and dismiss manually.
  • The static Snackbar component is render-controlled — conditionally include it in your template to show/hide.