eo-n/ui

Toast

Briefly displays transient messages or notifications.

import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
 
export function ToastDemo() {
  return (
    <Button
      onClick={() => {
        toastManager.add({
          title: "Profile Updated",
          description: "Your information has been saved.",
        });
      }}
    >
      Show Toast
    </Button>
  );
}

Installation

npm install  npx shadcn@latest add @eo-n/toast 

Usage

Add the ToastProvider to your app layout.

layout.tsx
import { ToastProvider } from "@/components/ui/toast";

<html lang="en">
  <body>
    <ToastProvider>{children}</ToastProvider>
  </body>
</html>;

Then use toastManager to show toasts from anywhere in your app:

import { toastManager } from "@/components/ui/toast";

toastManager.add({
  title: "Profile Updated",
  description: "Your changes have been saved.",
});

Note

The toastManager can be called from anywhere, including outside of React components. No hooks required.

Examples

With Action

import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
 
export function ToastWithAction() {
  return (
    <Button
      variant="destructive"
      onClick={() => {
        const id = toastManager.add({
          title: "Item deleted",
          description: "The file has been moved to trash.",
          actionProps: {
            children: "Undo",
            onClick: () => {
              toastManager.close(id);
              toastManager.add({ title: "Item restored" });
            },
          },
        });
      }}
    >
      Delete Item
    </Button>
  );
}

Promise

import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
 
export function ToastPromise() {
  const handleClick = () => {
    toastManager.promise(
      new Promise((resolve) => {
        setTimeout(() => resolve("Done"), 3000);
      }),
      {
        loading: {
          title: "Loading...",
        },
        success: {
          title: "Success!",
        },
        error: {
          title: "Error",
        },
      }
    );
  };
 
  return <Button onClick={handleClick}>Show Toast</Button>;
}

Varying Height

Demonstrates stacking behavior with toasts of different content lengths.

import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
 
export function ToastVaryingHeight() {
  return (
    <Button
      onClick={() => {
        const description = TEXTS[Math.floor(Math.random() * TEXTS.length)];
        toastManager.add({
          title: "Varying Height",
          description,
        });
      }}
    >
      Show Toast
    </Button>
  );
}
 
const TEXTS = [
  "Short message.",
  "A bit longer message that spans two lines.",
  "This is a longer description that intentionally takes more vertical space to demonstrate stacking with varying heights.",
  "An even longer description that should span multiple lines so we can verify the clamped collapsed height and smooth expansion animation when hovering or focusing the viewport.",
];

Anchored

Anchored toasts attach to a specific element on the page, useful for contextual feedback like copy-to-clipboard actions.

"use client";
 
import * as React from "react";
import { Check, Copy } from "lucide-react";
 
import { Button } from "@/components/ui/button";
import { anchoredToastManager } from "@/components/ui/toast";
 
export function ToastAnchored() {
  const [copied, setCopied] = React.useState(false);
  const buttonRef = React.useRef<HTMLButtonElement | null>(null);
 
  function handleCopy() {
    setCopied(true);
 
    anchoredToastManager.add({
      description: "Copied to clipboard!",
      positionerProps: {
        anchor: buttonRef.current,
        sideOffset: 8,
      },
      timeout: 1500,
      onClose() {
        setCopied(false);
      },
    });
  }
 
  return (
    <Button
      ref={buttonRef}
      variant="outline"
      size="icon"
      onClick={handleCopy}
      disabled={copied}
      aria-label="Copy to clipboard"
    >
      {copied ? <Check className="size-4" /> : <Copy className="size-4" />}
    </Button>
  );
}

Usage

Add the AnchoredToastProvider to your app layout (can be nested with ToastProvider):

layout.tsx
import { AnchoredToastProvider, ToastProvider } from "@/components/ui/toast";

<ToastProvider>
  <AnchoredToastProvider>{children}</AnchoredToastProvider>
</ToastProvider>;

Then use anchoredToastManager with a ref to the anchor element:

import { useRef, useState } from "react";

import { anchoredToastManager } from "@/components/ui/toast";

function CopyButton() {
  const [copied, setCopied] = useState(false);
  const buttonRef = useRef<HTMLButtonElement>(null);

  function handleCopy() {
    setCopied(true);

    anchoredToastManager.add({
      description: "Copied to clipboard!",
      positionerProps: {
        anchor: buttonRef.current,
        sideOffset: 8,
      },
      timeout: 1500,
      onClose() {
        setCopied(false);
      },
    });
  }

  return (
    <button ref={buttonRef} onClick={handleCopy}>
      Copy
    </button>
  );
}

API Reference

Provider

Provides settings for how toasts appear and behave.

PropTypeDefault
position
"top-center" | "top-right" | "top-left" | "bottom-center" | "bottom-right" | "bottom-left"
bottom-right
richColors?
boolean
false
closeButton?
boolean
false
timeout?
number
5000
limit?
number
3
toastManager?
ToastManager
-