Toast stack

Stack short results at the edge until you reach for them.

toastnotificationsonner
Preview
Toast stack preview

    When to use

    • App-wide notifications for async results, errors, and undo.
    • Loading toasts that morph into success or error when work finishes.
    • Toasts with an action button, like View or Undo.

    When not to use

    • Use toast for a single locally controlled confirmation.
    • Use alert for persistent messages tied to a page.
    • Use notification-center for a history people can come back to.

    shadcn CLI

    Also installs (1)

    • manicat-foundation
    Manual install(+)
    registry.jsonjson
    {
      "$schema": "https://ui.shadcn.com/schema/registry-item.json",
      "name": "toast-stack",
      "type": "registry:ui",
      "title": "Toast stack",
      "description": "Stack short results at the edge until you reach for them.",
      "categories": [
        "feedback"
      ],
      "dependencies": [
        "lucide-react",
        "motion"
      ],
      "registryDependencies": [
        "https://manicat.viraza.net/r/manicat-foundation.json"
      ],
      "files": [
        {
          "path": "registry/components/toast-stack/toast-stack.tsx",
          "type": "registry:component",
          "target": "@components/manicat/toast-stack/toast-stack.tsx"
        },
        {
          "path": "registry/components/toast-stack/toast-stack.module.css",
          "type": "registry:component",
          "target": "@components/manicat/toast-stack/toast-stack.module.css"
        }
      ],
      "docs": "Docs and live preview: https://uiarc.dev/components/toast-stack",
      "meta": {
        "tier": "free",
        "kind": "component",
        "docs": "https://manicat.viraza.net/components/toast-stack",
        "markdown": "https://uiarc.dev/components/toast-stack/markdown",
        "tags": [
          "toast",
          "notification",
          "sonner"
        ]
      }
    }

    Usage

    Install the component, then copy this into your app. Change the content and the props to fit your screen.

    example.tsxtsx
    "use client";
    
    import { useState } from "react";
    import { ToastStack, ToastStackProvider, useToastStack } from "@/components/manicat/toast-stack/toast-stack";
    import { Button } from "@/components/manicat/button/button";
    
    function ToastStackDemoInner() {
      const { toast, update, dismiss } = useToastStack();
      const [count, setCount] = useState(0);
    
      return (
        <div className="flex flex-wrap items-center gap-3">
          <Button
            onClick={() => {
              const id = toast({ title: "Publishing", description: "Uploading assets.", type: "loading" });
              setTimeout(() => update(id, { title: "Published", description: "arc-library is live.", type: "success" }), 1400);
            }}
          >
            Publish with a morphing toast
          </Button>
          <Button
            variant="secondary"
            onClick={() => {
              setCount((value) => value + 1);
              toast({ title: `Undo delete (${count + 1})`, type: "info", action: { label: "Undo", onClick: () => dismiss() } });
            }}
          >
            Toast with an action
          </Button>
          <ToastStack contained position="bottom-center" />
        </div>
      );
    }
    
    export function ToastStackExample() {
      return (
        <ToastStackProvider>
          <ToastStackDemoInner />
        </ToastStackProvider>
      );
    }
    

    Examples

    Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

    Toasts from anywhere Mount the provider once, then call toast() from any handler; actions like Undo live inside the toast.

    Toasts from anywhere Mount the provider once, then call toast() from any handler; actions like Undo live inside the toast. preview

      Keyboard interactions

      KeysAction
      Alt+TMoves focus to the front toast and fans out the stack.
      TabMoves between toast actions and dismiss buttons.
      EscapeDismisses the focused toast.

      Accessibility

      • The viewport is a labelled section with aria-live="polite" and aria-relevant="additions text", so new and updated toasts are announced.
      • Each toast prefixes a screen-reader-only type label such as "Error:"; outgoing copy is aria-hidden while it fades.
      • Hidden and leaving toasts are inert. When a focused toast closes, keyboard focus moves to the next toast or back to where it came from.
      • Timers pause while the stack is hovered, focused, dragged, or the tab is hidden.

      Motion

      • New toasts rise from their own height below the edge; older ones tuck behind at 14px peeks and 0.05 scale steps, and fan out on a morph spring.
      • Swiping right follows the finger and throws with velocity; left rubber-bands. Updates morph the icon, copy, height, and action width in place.
      • Reduced motion jumps positions and uses short opacity fades.

      Responsive behavior

      • The viewport is min(22.5rem, 100% minus a 32px gutter) wide; below 30rem it centers at the bottom regardless of position.
      • Below a 21rem toast width the action button drops under the copy instead of squeezing it.
      • On touch, a tap fans out the stack, and a right swipe dismisses a toast.

      Performance

      • At most 12 toasts are kept by default and only 3 show; each has its own ResizeObserver for height.
      • Timers pause while hovered, focused, dragged, or when the tab is hidden.

      Notes for AI

      • Default for app-wide notifications, async results, and undo. Use toast for a single locally controlled confirmation and alert for persistent inline messages.
      • Render ToastStack once inside ToastStackProvider and call useToastStack anywhere below. Reuse an id or call update to morph loading into success or error.