Toast with motion: toasts spring in and the stack glides to make room, a changing toast pops its icon over, and a swipe sends one away.
$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-toast.jsonInstallation
$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-toast.jsonAdds components/dashral/motion/toast.tsx, the Toast it builds on (in components/dashral/), motion and the motion files every motion component shares. With the registry in your components.json:
$ pnpm dlx shadcn@latest add @dashral/motion-toastUsage
The same as Toast, imported from components/dashral/motion/: swap the import and it moves.
// Once, near the root of your app:
import { Toaster } from "@/components/dashral/motion/toast"
<Toaster position="bottom-right" />
// Anywhere:
import { toast } from "@/components/dashral/motion/toast"
toast.success("Changes saved")
toast("Archived", { action: { label: "Undo", onClick: restore } })Examples
Every kind
Default, success, error with an action, and a promise that goes from Publishing… to Published in one toast. Swipe one sideways to close it.
Compared with Default
Toasts slide in from the corner and stack.
Motion
Toasts fold into a stack that spreads on hover; a new one springs in and pushes it back; a toast that changes (loading to done) pops its icon over and crosses its words; a swipe sideways sends it off.
How the two editions differ in general, and which to use where: Motion.
API reference
Its own API, in place of Toast's:
Toaster
Mount it once near the root of your app.
| Prop | Type | Default |
|---|---|---|
position | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right"Where the toasts gather. | "bottom-right" |
visibleToasts | numberHow many show at once; older ones wait their turn. | 3 |
duration | numberHow long a toast stays, in milliseconds, unless it says otherwise. | 4000 |
className | stringThe stack's classes. | — |
toast(message, options)
Shows a toast and returns its id. Also toast.success, .info, .warning, .error and .loading (with the type's icon), toast.promise(promise, { loading, success, error }) and toast.dismiss(id?).
| Prop | Type | Default |
|---|---|---|
description | ReactNodeA second line under the message. | — |
action | { label, onClick }A button in the toast; clicking it also closes the toast. | — |
duration | numberHow long it stays, in milliseconds; Infinity keeps it until it's closed. Loading toasts stay by default. | Toaster's |
id | string | numberAn existing toast's id, to change it in place (Saving… to Saved). | — |
icon | ReactNodeIn place of the type's icon. | — |
onDismiss | () => voidCalled when it closes, by its timer, a swipe or toast.dismiss(). | — |
Its own toast() and <Toaster />, without sonner: the calls are the same as the default edition's.
Motion
- Toasts fold into a stack: the newest in front, the two before it peeking out behind (10px each), smaller and fainter, cut to the front one's height. Hover or tap and the stack spreads into a list on a spring, holding the timers; leave and it folds back.
- A new toast springs in from its edge (--duration-standard, a hint of overshoot) and the toasts already there glide aside to make room; a closing one shrinks and fades (--duration-fast).
- A toast that changes (toast.promise, or the same id again) pops its new icon over the old and crosses its words over, and the toasts around it glide to its new size.
- Drag one sideways and it follows; let go past 80px, or flick it, and it leaves that way; short of it, it springs back.
- Hovering the stack holds the timers. With reduced motion toasts only fade and don't drag.