Sonner
A managed toast stack. Mount one Toaster, call toast() from anywhere. Toasts pile up, spread on hover and swipe away.
Use Toaster and toast() when you want toasts triggered from code and managed for you: the queue, position, timer, stacking and swipe-to-dismiss. For a single static card you place yourself, use Toast.
Stacking
Add a few. They pile up with the newest in front; hover the pile to spread it out, then drag the front toast down and let go.
Types
Rich colors
By default types differ only by icon. richColors tints the whole toast with the success, info, warning or danger tokens. Default and loading toasts are never tinted.
Actions and close button
Promise
One toast follows an async task: loading first, then success or error, in the same place.
Updating and dismissing
Position
Six positions, 24px from the viewport edge (16px on phones, where toasts fill the width). Pick one, then add a toast.
Motion
Movement runs 400ms on a gentle ease. With reduced motion on, toasts keep the fades and drop the movement.
| Moment | What happens |
|---|---|
| A toast arrives | Rises in from below its own height while fading in. 400ms. |
| More toasts arrive | Older toasts lift 14px and shrink 5% per level, with their content hidden so only an edge shows. Three are visible. |
| Hover or focus | The pile spreads into a list, 14px between toasts, each at its own height. Timers pause. |
| Pointer leaves | The list folds back into the pile and the timers resume. |
| Swipe | The toast follows the pointer. Release under 45px and it springs back; past 45px, or a quick flick, it flies out in 200ms. The direction follows the position. |
| Lifetime | 4 seconds by default. Loading toasts stay until you update them. |
Accessibility
- Toasts live in a labelled notifications region. Each one is announced politely; errors and
importanttoasts are announced assertively. - Press alt + T to focus the stack, Esc to fold it. Focus spreads the pile and pauses the timers, like hover.
- Every toast can be dismissed with Delete while focused, and the close button and actions are real buttons with focus rings.
- Don't put anything people must act on in a toast. It disappears after 4 seconds; use Alert or Modal instead.
toast()
| Call | What it does |
|---|---|
| toast(title, options?) | Default toast, no icon. Returns the toast id. |
| toast.success / info / warning / error | Same signature, with the type's icon (tinted when richColors is on). |
| toast.loading(title, options?) | A toast with a spinner and no timer. Update or dismiss it by id. |
| toast.promise(promise, messages) | Shows messages.loading, then success or error from the same toast. success and error can be functions of the result. |
| toast.dismiss(id?) | Closes one toast, or all of them without an id. |
Toast options
| Prop | Type | Default | Description |
|---|---|---|---|
| id | string | number | Reuse an id to update a toast in place instead of adding a new one. | |
| description | ReactNode | Secondary line under the title. | |
| icon | ReactNode | null | Replaces the type icon. null shows no icon. | |
| action | { label, onClick } | Primary button, right-most. Closes the toast after onClick unless you call event.preventDefault(). | |
| cancel | { label, onClick? } | Secondary button, left of the action. Closes the toast. | |
| closeButton | boolean | Shows the round close button on this toast. | |
| duration | number | 4000 | Overrides the Toaster's duration. Loading toasts have no timer. |
| dismissible | boolean | true | When false the toast cannot be swiped away or closed by the user. |
| important | boolean | false | Announces the toast assertively. Errors always are. |
| richColors | boolean | Tints this toast by its type. | |
| onDismiss | (toast) => void | Called when the user closes it (close button, cancel, swipe) or toast.dismiss() is called. | |
| onAutoClose | (toast) => void | Called when the toast closes itself after its duration. | |
| className | string | Extra class on the toast element. |
Toaster props
| Prop | Type | Default | Description |
|---|---|---|---|
| position | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'bottom-right' | Where the stack sits. Top positions stack downwards, bottom positions upwards. |
| richColors | boolean | false | Tints success, info, warning and error toasts by type. |
| closeButton | boolean | false | Shows the round close button on every toast. |
| expand | boolean | false | Keeps the stack spread out instead of piled up. |
| visibleToasts | number | 3 | How many toasts show at once. Older ones fade out. |
| duration | number | 4000 | Milliseconds before a toast closes itself. Infinity keeps it open. |
| gap | number | 14 | Space between toasts when spread out, in px. |
| offset | number | string | 24 | Distance from the viewport edge. Numbers are px; strings are any CSS length. |
| mobileOffset | number | string | 16 | Distance from the edge on screens 600px wide or less, where toasts fill the width. |
| hotkey | string[] | ['altKey', 'KeyT'] | Keys that focus the stack (alt+T). |
| containerAriaLabel | string | 'Notifications' | Accessible name of the notifications region. |
Why it works this way
Why
- It owns the queue, position, timers and stacking, so the app only says what happened. toast() works from handlers, hooks and plain modules without passing state around.
- Toasts pile up behind the newest and spread on hover, so a burst of notifications takes the room of one until you look at them.
Don't
- Don't use a toast for anything people must act on. It disappears after 4 seconds. Use Alert or Modal.
- Don't mount more than one Toaster. Every toast would show in all of them.
- Don't render Toast yourself for this. Toast is the static card; Toaster and toast() manage a stack of them.