Skip to content
WANCRAFT

G.56Data & Feedback

Toast Stack

Stacked notifications that peek behind each other, fan out on hover and swipe away.

NewReactMotionTailwindadvanced

Overview

Toast Stack pairs a typed useToastStack() hook with a ToastStack renderer. The newest toast sits in front while older ones scale down and peek behind it; hovering, focusing or tapping the stack fans it out into a list measured with ResizeObserver. Toasts can be success, error, info or loading, and promise() turns a loading toast into a success or error when the promise settles. Each toast can carry an action, is swiped away horizontally, and auto-dismisses on a timer that pauses (keeping its remaining time) while the stack is hovered or focused and while the tab is hidden. Messages are announced through persistent polite and assertive live regions.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/toast-stack.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/toast-stack.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/toast-stack.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { ToastStack, useToastStack } from "@/components/ui/toast-stack";

export function ToastStackExample() {
  const toaster = useToastStack();

  return (
    <>
      <button
        type="button"
        onClick={() => toaster.toast({ type: "success", title: "Changes saved", action: { label: "Undo", onClick: () => {} } })}
      >
        Save
      </button>
      <ToastStack toaster={toaster} />
    </>
  );
}

03Props

PropTypeDefaultDescription
toasterToastController—Controller from useToastStack(): { toasts, toast(options), update(id, options), dismiss(id?), promise(promise, messages) }.
position"top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right""bottom-right"Edge or corner the stack is anchored to.
durationnumber4000Default auto-dismiss delay in ms (per-toast duration overrides; Infinity persists).
visibleToastsnumber3Toasts visible in the collapsed stack.
expandbooleanfalseAlways show the stack expanded as a list.
swipeToDismissbooleantrueDismiss toasts with a horizontal swipe.
showProgressbooleantrueShow a hairline counting down each timer.
strategy"fixed" | "absolute""fixed"Anchor to the viewport, or to the nearest positioned ancestor.
offsetnumber20Distance from the anchored edges in px.
gapnumber10Gap between expanded toasts in px.
widthnumber356Stack width in px (shrinks on narrow screens).
accentstring"#FF7448"Info icon, action, hairline and focus colour.
labelstring"Notifications"Accessible name of the region.
closeLabelstring"Dismiss notification"Accessible name prefix of each dismiss button.
...propsHTMLAttributes<HTMLElement>—Native attributes are forwarded to the <section>.

04Features

  • 01Typed useToastStack() hook: toast(), update(), dismiss() and promise() for loading → resolved toasts
  • 02Collapsed stack with scaled, peeking older toasts; hover, focus or tap fans it out into a measured list
  • 03Swipe to dismiss with a velocity threshold
  • 04Auto-dismiss timers that pause and resume with their remaining time on hover, focus and hidden tabs
  • 05Success, error, info and loading types, optional action button, six positions
  • 06Fixed (viewport) or absolute (container) anchoring
motion · springmotion · layoutinput · hoverinput · draginput · clickinput · keyboardinput · focus

05Accessibility

  • Rendered as a labelled <section> region containing an ordered list of toasts.
  • Messages are announced through persistent live regions: role="status" (polite) and role="alert" (assertive) for errors.
  • Each dismiss button is labelled with the toast's title; Escape inside a toast dismisses it and keeps focus in the region.
  • Keyboard focus inside the stack expands it and pauses every timer, so nothing disappears mid-read.
  • Toasts faded out behind the collapsed stack are inert until it expands.
  • With prefers-reduced-motion toasts appear, move and leave without animation; the spinner stops rotating.

06Limitations

  • The controller is scoped to the component that calls useToastStack(); share it through props or context to toast from elsewhere.