Generates toast notifications.
Installation
Install the packages
Add the shadcn/ui component
Animate it
The CLI finds your shadcn/ui components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.
Usage
Import the primitive from Animate UI instead of Base UI. Everything else stays the same.
import { Toast } from '@animate-ui/react/primitives/base-ui/toast';
function ToastList() {
const { toasts } = Toast.useToastManager();
return toasts.map((toast) => (
<Toast.Root key={toast.id} toast={toast}>
<Toast.Content>
<Toast.Title />
<Toast.Description />
<Toast.Close aria-label="Close">×</Toast.Close>
</Toast.Content>
</Toast.Root>
));
}
export function Toaster({ children }: { children: React.ReactNode }) {
return (
<Toast.Provider>
{children}
<Toast.Portal>
<Toast.Viewport>
<ToastList />
</Toast.Viewport>
</Toast.Portal>
</Toast.Provider>
);
}Using the @base-ui/react umbrella import instead? The barrel mirrors it: import { Toast } from '@animate-ui/react/primitives/base-ui'.
Types
Set the type option to render a status icon (shadcn/ui's ToastIcon, data-slot="toast-icon"). Each type's icon shows in its own way once the toast has landed: the success check draws itself, the error cross draws itself and shakes, the others pop in.
Promise
toast.promise (or toast.update) changes one toast in place: its spinner turns into a drawn check (or cross), its text swaps through a light blur, and its height glides to the new content's.
Remaining time
A thin bar along each toast's bottom edge shows the time it has left, in the color of its type: its icon's (shadcn colors the error's), else its text's. progressBar={false} on the Provider (or on one Root) leaves it out.
<Toast.Provider progressBar={false}>…</Toast.Provider>The bar is [data-animate-ui-toast-progress-bar] inside [data-animate-ui-toast-progress], styled at zero specificity (2px, the toast's text color at 25%): any class or selector of yours wins, for instance [data-type=error] [data-animate-ui-toast-progress-bar] { background: var(--destructive) }.
Animation
Animate UI v1 had no toast. A toast rises from its edge like v1's Sheet, and lands; anchored toasts move like tooltips.
Base UI drives the stack, swipe-to-dismiss and the enter/exit itself, through CSS variables and its transition status, and waits for CSS transitions before removing a toast. So the Root animates with CSS transitions too (a JavaScript animation would fight the swipe), springs written as the linear() curve Motion itself would use, and sets them itself from its state, so AnimateUIProvider tempo applies.
- A toast rises from its edge, a full toast height away, from 0.96 and out of a 4px blur, fading in, on a spring (stiffness 280, damping 24): there in ~165ms, it goes ~4% past its rest and settles by ~525ms, so it lands rather than stops.
- Dismissed, it leaves the way it came, quicker: back through its edge, to 0.96 and into the blur, fading out, in 0.2s on the dismissal curve of dialogs and sheets (
cubic-bezier(0.33, 0.17, 0.67, 0.5)): it leaves at once, speeding away. - Its icon shows by type, once the toast has landed (after 0.2s): a success check draws its stroke in 0.3s like a checkbox's check; an error cross draws the same way and, half drawn, shakes "no" (3px, a few dying swings in 0.4s); any other icon (info, warning, the loading spinner) pops in from half its size out of a 2px blur (stiffness 500, damping 24). The icon is the first element of
data-slot="toast-icon"(shadcn/ui's). - An update changes it in place (
toast.update,toast.promise): the previous icon (a spinner still spinning) shrinks into a 2px blur while the new one shows as above, so the spinner turns into a drawn check or cross; text that changed swaps through the same blur (0.15s, ease-out); the toast's height glides to its new content's on its spring. Updated while it's still coming in, it keeps rising; dismissed mid-update, it just leaves. - Stack: collapsed, each toast behind the front one scales down by 0.05 per step (never below 0.9) and peeks out by 0.75rem; its content fades out (
data-behind) in 200ms, ease-out. Hovering or focusing the viewport unfolds the stack with a 0.75rem gap front to back, each toast 30ms after the one in front of it, its content fading back in with it, and it folds back the same way. The stack moves on the same spring, and toasts added quickly retarget instead of restarting. - A swipe follows the finger, fading as it goes (gone at its own size away). Dragged the way it can't be swiped, it rubber-bands: it follows at half the finger's pace at first, then resists up to 48px. Let go short of the threshold, it springs back; past it, it's thrown the way it was swiped at the finger's speed (linear, its own size at that speed, between 80 and 360ms, like a drawer's swipe dismissal).
- The remaining-time bar (
progressBar) shrinks linearly over the toast's timeout, in real time (tempodoesn't pace a timer). It pauses whenever Base UI pauses the timer (the stack hovered or focused, the window in the background) and resumes from there; a loading toast, which has no timer, has no bar, and resolved, its bar starts full. Only the front toast's shows while the stack is collapsed. It takes the color of the toast's type: its icon's color at 80% when the icon has its own (the error's red in shadcn), else the text's at 25%. - Toasts swiped up (
swipeDirection="up") live at the top edge: they rise from it and leave through it. Anchored toasts (Toast.Positioner) are small bubbles, like tooltips: they grow from their anchor from 0.7 with a fade, on a spring (stiffness 400, damping 29), and leave the same way. - shadcn/ui's own CSS stack (its
[transform:…]and[transition:…]classes and the variables only they read) would fight these: the CLI removes them. - Under reduced motion, toasts fade in place out of their blur and the stack changes at once (no cascade); icons only fade (no drawing, popping or shaking), and a toast updated in place swaps its text and icon through their blur without shrinking. A swipe still follows the finger and throws the toast along the swipe, and the remaining-time bar still shrinks: it tells time, it doesn't move anything across the screen.
API Reference
Every part takes exactly the props of Base UI's Toast. Animate UI adds the animation props below. They are CSS transitions: a spring becomes a linear() curve, a tween keeps its duration and cubic-bezier or named ease.
Provider
Base UI props, plus:
| Prop | Type | Default |
|---|---|---|
progressBar? | boolean | true |
Viewport, Portal, Positioner, Title, Description, Action, Close, Arrow
Base UI props only.
Root
Base UI props, plus:
| Prop | Type | Default |
|---|---|---|
transition? | Transition | { type: 'spring', stiffness: 280, damping: 24 }; anchored: { type: 'spring', stiffness: 400, damping: 29 } |
exitTransition? | Transition | { duration: 0.2, ease: [0.33, 0.17, 0.67, 0.5] }; anchored: transition |
progressBar? | boolean | the Provider's progressBar |
The Root sets its transform, filter, transition and opacity through Base UI's style prop; a style of your own (object or function) still applies on top. Its swipe handlers (onPointerDown, onPointerMove, onPointerUp, onPointerCancel) still run yours.
Content
Base UI props, plus:
| Prop | Type | Default |
|---|---|---|
transition? | Transition | { duration: 0.2, ease: [0.23, 1, 0.32, 1] } |