Sonner
Transient toast notifications built on sonner. Mount the Toaster once at the root and fire toast() from anywhere.
Overview
Sonner is the transient feedback primitive: "Saved", "Invite sent", "Failed - try again". Mount Toaster once at the application root (typically in app/layout.tsx) and fire toast() from anywhere in the tree without an additional provider.
For persistent in-flow messages use Alert; for critical errors that need acknowledgment use AlertDialog. Toasts should be non-blocking and dismissable.
The Gremorie Toaster follows the document's .dark class automatically and ships with Gremorie icons for the success, info, warning, error, and loading toast types.
Preview
'use client';import { Button } from '@gremorie/rx-forms';import { Toaster, toast } from '@gremorie/rx-overlays';export function SonnerPreview() { return ( <> <Button variant="outline" onClick={() => toast('Primitive added', { description: 'rx-message is now available in your project.', }) } > Show toast </Button> <Toaster /> </> );}Anatomy
Toaster singleton mount that renders the toast region
├─ toast(message, opts) imperative API fired from anywhere
│ ├─ toast.success positive confirmation
│ ├─ toast.error failure the user should know about
│ ├─ toast.warning risky state, not yet a failure
│ ├─ toast.info neutral system message
│ ├─ toast.loading pending operation
│ └─ toast.promise auto-transitions loading → success/error
└─ action optional inline action button on a toastInstallation
bash npx gremorie@latest add rx-sonner bash pnpm dlx gremorie@latest add rx-sonner bash yarn dlx gremorie@latest add rx-sonner bash bunx --bun gremorie@latest add rx-sonner Usage
Mount the Toaster once at the root of your application:
// app/layout.tsx
import { Toaster } from '@gremorie/rx-overlays';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Toaster />
</body>
</html>
);
}Then fire toasts from any client component:
'use client';
import { Button } from '@gremorie/rx-forms';
import { toast } from '@gremorie/rx-overlays';
export function SaveButton() {
return (
<Button
onClick={() =>
toast.success('Saved', {
description: 'Your changes are live.',
})
}
>
Save
</Button>
);
}The Angular edition of this component ships from source today (see the workbench for the side-by-side); its registry entry is coming next.
API
<Toaster>
Wraps sonner's Toaster with Gremorie tokens and icons. Mount once per app.
| Prop | Type | Default | Description |
|---|---|---|---|
theme | "light" | "dark" | "system" | auto-detected from .dark class | Toast theme. Pass explicitly to override the document-class detection. |
position | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center" | "bottom-right" | Where toasts stack. |
richColors | boolean | false | Use color-coded backgrounds for success, error, warning, info. |
expand | boolean | false | Always expand the stack instead of collapsing on hover. |
duration | number | 4000 | Default toast lifetime in milliseconds. |
closeButton | boolean | false | Render a close button on every toast. |
gap | number | 14 | Vertical gap between stacked toasts. |
offset | string | number | 32 | Distance from the viewport edge. |
visibleToasts | number | 3 | Max toasts visible at once. |
All other sonner ToasterProps are forwarded.
toast(message, options?)
Default toast. Returns a toast id you can pass to toast.dismiss().
| Option | Type | Description |
|---|---|---|
description | string | ReactNode | Secondary line below the message. |
action | { label: string; onClick: () => void } | Single inline action button. |
cancel | { label: string; onClick?: () => void } | Secondary cancel/undo button. |
duration | number | Override the default duration. |
id | string | number | Custom id for later updates or dismissal. |
onDismiss | (toast) => void | Fired when the toast is dismissed. |
onAutoClose | (toast) => void | Fired when the toast auto-closes. |
Typed shortcuts
| Method | Icon | When to use |
|---|---|---|
toast.success(message, options) | CircleCheck | Positive confirmation. |
toast.error(message, options) | OctagonX | Failure that the user should know about. |
toast.warning(message, options) | TriangleAlert | Risky state that is not yet a failure. |
toast.info(message, options) | Info | Neutral system message. |
toast.loading(message, options) | Loader2 (animated) | Pending operation. Pair with toast.success on resolution. |
toast.promise(promise, opts) | Loader2 then resolved icon | Auto-transitions loading → success/error from a Promise. |
toast.custom(jsx, options) | - | Fully custom toast body. |
toast.dismiss(id?) | - | Dismiss a specific toast or all. |
toast.promise signature:
toast.promise(savePost(), {
loading: 'Saving...',
success: (data) => `Saved post #${data.id}`,
error: 'Save failed',
});Composition
<Toaster>at the app root (app/layout.tsxfor Next.js).toast()fired imperatively from event handlers, async flows, or effects.- Optional
actionfor undo or follow-up navigation. toast.promisefor any awaitable flow that should communicate progress.
Variations
Toast types
Each button fires a different toast: default, success, error, one with an inline action, and a promise that transitions loading to success.
'use client';import { Button } from '@gremorie/rx-forms';import { Toaster, toast } from '@gremorie/rx-overlays';export function SonnerVariantsPreview() { return ( <> <div className="flex flex-wrap gap-2"> <Button variant="outline" onClick={() => toast('Event created', { description: 'Friday, June 20 at 10:00 AM', }) } > Default </Button> <Button variant="outline" onClick={() => toast.success('Changes saved', { description: 'Your profile is up to date.', }) } > Success </Button> <Button variant="outline" onClick={() => toast.error('Save failed', { description: 'Check your connection and try again.', }) } > Error </Button> <Button variant="outline" onClick={() => toast('Item archived', { description: 'You can restore it within 30 days.', action: { label: 'Undo', onClick: () => toast('Restored'), }, }) } > With action </Button> <Button variant="outline" onClick={() => toast.promise(new Promise((resolve) => setTimeout(resolve, 1500)), { loading: 'Publishing post...', success: 'Post is live', error: 'Publish failed', }) } > Promise </Button> </div> <Toaster /> </> );}Success with undo
The canonical "saved with undo" pattern.
toast.success('Item archived', {
description: 'You can restore it within 30 days.',
action: {
label: 'Undo',
onClick: () => restore(item.id),
},
});Promise
Drive loading, success, and error states from a single call.
toast.promise(api.publish(post), {
loading: 'Publishing post...',
success: (post) => `${post.title} is live`,
error: 'Publish failed - retry?',
});Custom position with rich colors
Top-center stack with semantic backgrounds.
<Toaster position="top-center" richColors duration={5000} />Persistent error toast
Disable auto-close for failures that require user attention.
toast.error('Connection lost', {
description: 'Reconnecting...',
duration: Infinity,
action: {
label: 'Retry now',
onClick: () => reconnect(),
},
});Accessibility
- Live region: toasts announce via
aria-live="polite"by default; assistive tech reads them without interrupting. - Errors:
toast.errorusesaria-live="assertive"so failures are read immediately. - Dismiss:
Escdismisses the most recent toast when one is focused. - Hover and focus: hovering or focusing the stack pauses the auto-dismiss timer.
- Keyboard: action buttons inside toasts are reachable via
Tabonce the stack is focused. - Theme: the
Toasterwatches the document.darkclass automatically; passthemeexplicitly when mounting in a non-themed iframe or sandbox. - Touch: swipe-to-dismiss is implemented by sonner; keyboard users can dismiss via
Esc. - Reduced motion: enter/exit animations honor
prefers-reduced-motion.
Related
- Alert - persistent in-flow message that lives in the layout.
- Alert Dialog - interruptive confirmation for irreversible actions.
- Dialog - focused modal for short flows.