A one-off celebration — deploy shipped, goal reached, payment cleared — that enters on the playful spring tier and clears itself, with a staggered group for when several land at once.
One toast for a single win, a staggered group when several land at once.
pnpm dlx shadcn@latest add https://matos-ui.com/r/achievement-toast.jsonimport {
AchievementToast,
AchievementToastGroup,
type AchievementData
} from '@/components/matos-ui/achievement-toast'const [open, setOpen] = useState(false)
;<AchievementToast
open={open}
onOpenChange={setOpen}
title="Deploy successful"
description="matos-ui.com is live on production."
icon={<CheckCircle2 />}
/>When several wins land together, AchievementToastGroup takes the array and
staggers them in:
const [achievements, setAchievements] = useState<AchievementData[]>([])
function dismiss(id: string) {
setAchievements(current => current.filter(item => item.id !== id))
}
;<AchievementToastGroup achievements={achievements} onDismiss={dismiss} />Notification Stack is informational and continuous: things keep arriving, and the tone stays neutral because the next one is always coming. Achievement Toast is the opposite shape — a single moment that happened once and is worth marking. That difference is what the motion is spending itself on. If you find yourself firing one of these on every save, it wants to be a notification instead.
spring.playful — chosen by hand, not resolved from the
offset. motionForOffset deliberately can't return this tier, so nothing
becomes celebratory by accident; a component has to ask for it. This is the
use case that justifies the tier existing.Elevated offset={3}, the same rung a dialog-adjacent overlay
sits on. Its shadow ring draws the edge, so there's no border on top of it.document.body and resets the surface substrate to
the page before applying its offset. A toast pinned to the viewport corner
isn't visually sitting on whatever card rendered it, so offset={3} has to
mean the same rung from anywhere in the tree — the opposite of what
Stacked Dialog wants, where inheriting
the substrate is the whole point of nesting.AchievementToastGroup uses staggerContainer("playful") — the widest gap
in the scale (80ms), so each item reads as its own arrival rather than as a
list appearing. As in Live Queue, the stagger
orchestrates what's in the array on mount; items appended later enter
individually without a queued-up delay.attentionPulse, cued by the entrance completing
rather than by mount — so it reads as the toast settling, not as a second
thing happening at the same time.exit.duration (0.2s), not its full entrance
duration (0.32s). useExitAnimation arms its force-unmount fallback off
that same number, so an exit animating for the longer duration would get cut
short by its own guard.useExitAnimation still force-unmounts on a timer, so a backgrounded tab
that stalls the animation can't leave the portal mounted forever.prefers-reduced-motion drops the bounce, the travel, the stagger, and the
icon pulse; the toast still appears and clears on opacity alone.| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Whether the toast is showing. |
onOpenChange | (open: boolean) => void | — | Called on auto-dismiss and on the close button. |
title | string | — | The thing that was achieved. |
description | string? | — | Supporting line, clamped to two lines. |
icon | ReactNode? | — | Rendered in its own elevated frame; pulses once on landing. |
duration | number | 4000 | ms on screen. 0 keeps it until dismissed by hand. |
position | AchievementToastPosition | "bottom-right" | Which corner the viewport pins to. |
className | string? | — | Applied to the card, not the viewport. |
| Prop | Type | Default | Description |
|---|---|---|---|
achievements | AchievementData[] | — | The current run, in order. |
onDismiss | (id: string) => void | — | Called per item, on auto-dismiss or close. |
duration | number | 4000 | ms on screen, applied to each item. |
position | AchievementToastPosition | "bottom-right" | Which corner the viewport pins to. |
className | string? | — | Applied to the viewport. |
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier. |
title | string | The thing that was achieved. |
description | string? | Supporting line. |
icon | ReactNode? | Icon for the elevated frame. |
"top-left" | "top-right" | "bottom-left" | "bottom-right"
Install Matos UI
Choose a package manager and copy one command for every component.