Toast + Notifications
Sonner-inspired stack manager for transient notifications. Per the answered design question recorded with this component: top-stack with auto-dismiss, hover pauses the timer.
Two layers:
- Toast — the visual primitive. A single
<li class="toast">. - Notifications — the
<ol class="notifications">container that hosts a stack of Toasts and provides the runtime API.
When to use
- Confirm successful operations ("Saved", "Copied").
- Surface non-blocking errors ("Couldn't reach server").
- Show ambient async progress ("3 items moved").
For blocking confirmations, use Dialog. For inline form errors, use
the Error element inside a Field-group.
API
import { createNotifications } from "@malevich/components";
const notify = createNotifications({ position: "bottom-right" });
notify.show({
title: "Saved",
description: "Your changes were saved.",
variant: "success",
duration: 5000, // ms; 0 = persist until dismissed
});
// Later:
notify.dismissAll();
If a <ol class="notifications"> element already exists with the
matching data-position, the runtime reuses it. Otherwise it
creates one and appends to <body>.
Variants
| Variant | Class | Default icon |
|---|---|---|
| Neutral | .toast |
• |
| Info | .toast.-info |
ℹ |
| Success | .toast.-success |
✓ |
| Warning | .toast.-warning |
⚠ |
| Danger | .toast.-danger |
⚠ |
Variants color the left edge and the icon; the rest stays neutral so the message reads clearly against the surface.
Positions
data-position on the container:
bottom-right(default)bottom-lefttop-righttop-left
Bottom positions stack newest-at-bottom (column-reverse) so the most recent message appears closest to the viewport edge.
Anatomy
Authored (static) toast for tests / docs:
<ol class="notifications" data-position="bottom-right"
aria-live="polite" aria-label="Notifications">
<li class="toast -success">
<span class="toast__icon" aria-hidden="true">✓</span>
<div class="toast__content">
<strong class="toast__title">Saved</strong>
<p class="toast__description">Your changes were saved.</p>
</div>
<button class="toast__dismiss" aria-label="Dismiss">×</button>
</li>
</ol>
Programmatic (typical):
notify.show({ title: "Sent", variant: "success" });
notify.show({ title: "Couldn't save", description: "Try again later.", variant: "danger" });
Tokens used
--color-surface-float— background--color-info/--color-success/--color-warning/--color-danger— variant accent--color-ink-strong,--color-ink-regular,--color-ink-subtle--color-border-default— border--color-surface-canvas— dismiss hover--shadow-float— elevation--radius-card,--radius-button--space-inset-block-m,--space-gap-elements-s--size-control-s— icon/dismiss size--border-width-{hairline,emphasis,focus}--font-body-{s,m}-*--motion-fast,--motion-base,--motion-easing-default
Component-tier (defined inline on .notifications)
--notifications-gap— gap between stacked toasts--notifications-inset— distance from viewport edge--notifications-width— toast width
Accessibility
- The
<ol>container carriesaria-live="polite"so screen readers announce new toasts non-disruptively. For critical alerts, setaria-live="assertive"on the container (not on individual toasts). - The container has
aria-label="Notifications". - Each toast's dismiss button has
aria-label="Dismiss". - Auto-dismiss should not be used for critical messages — users who
rely on screen readers may not hear the announcement before the
toast disappears. For critical content, use Dialog or
aria-live="assertive"+duration: 0(persist).
Edge cases
- Hover pause: the timer pauses on
mouseenter, resumes onmouseleave. Keyboard users get the full duration each time. - Many concurrent toasts: the stack grows. Authors can cap by
calling
dismiss()on the oldest id (or calldismissAll()). - Server-rendered toasts: authoring a static
<li class="toast">inside the<ol class="notifications">works without runtime — the visual styles are CSS-only. Only auto-dismiss and dismiss button wiring require runtime.
Do
- Keep titles short — they're announced verbatim.
- Use the appropriate variant so color + icon convey the message type at a glance.
- Persist (
duration: 0) for messages the user must read.
Don't
- Don't use Toast for blocking confirmations — use Dialog.
- Don't stack more than ~3 visible toasts at once. Consider grouping.
- Don't rely on auto-dismiss for important content — color and icon do not replace text.