npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eglador-ui-react-toast

v1.0.0-alpha.1

Published

Stacked, position-aware toast notifications for React — imperative API + compound subcomponents, headless hook, zero runtime dependencies

Readme

eglador-ui-react-toast

npm version npm downloads license zero runtime deps tailwind v4 react >= 18 typescript

Sonner-style toast notifications for React — imperative toast() API, six variants, six positions, stacking with hover-expand, swipe-to-dismiss, promise pattern, compound subcomponents, and a headless hook. Tailwind CSS v4, zero runtime dependencies.

Features

  • Imperative APItoast() / .success / .error / .warning / .info / .loading / .custom / .promise from any component, no provider, no context wiring
  • Six variants — differentiated by icon shape only, surface stays in zinc tones
  • Six positions — top/bottom × left/center/right; per-toast override allowed
  • Stacking — older toasts collapse behind, hover the stack to expand
  • Swipe-to-dismiss — pointer-driven horizontal drag (mouse + touch)
  • Auto-dismiss — configurable duration, paused on hover, paused on window blur
  • Promise patterntoast.promise(p, { loading, success, error }) transitions in place
  • Update by id — same id replaces the existing toast (loading → success transitions)
  • Compound subcomponentsToast.Icon / Title / Description / Action / Cancel / CloseButton
  • Headless hookuseToaster() exposes the live store for fully custom UIs
  • Render-prop on <Toaster> — swap the default toast renderer entirely
  • Reduced-motion friendly — animations disable under prefers-reduced-motion: reduce
  • Accessiblerole="status" for default / info / success / loading, role="alert" for error / warning, aria-live set automatically
  • TypeScript-first — generic over promise data, every prop documented inline
  • Zero runtime dependencies — only clsx + tailwind-merge, both pre-bundled

Installation

npm install eglador-ui-react-toast

Peer dependencies: react >= 18 · react-dom >= 18 · tailwindcss ^4

Setup

Add the following to your global stylesheet so Tailwind picks up the component classes:

@import "tailwindcss";
@source "../node_modules/eglador-ui-react-toast";

The @source path is relative to the CSS file location:

| Framework | CSS file location | Path | |---|---|---| | Next.js (App Router) | app/globals.css | ../node_modules/eglador-ui-react-toast | | Next.js (src/) | src/app/globals.css | ../../node_modules/eglador-ui-react-toast | | Vite | src/index.css | ../node_modules/eglador-ui-react-toast |

Quick Start

"use client";

import { Toaster, toast } from "eglador-ui-react-toast";

export function App() {
  return (
    <>
      {/* 1. Mount once at the app root */}
      <Toaster position="bottom-right" />

      {/* 2. Trigger from anywhere */}
      <button onClick={() => toast.success("Saved successfully")}>
        Save
      </button>
    </>
  );
}

API

Exports

| Export | Purpose | |---|---| | Toaster | Root component — render once at the app top level | | toast | Imperative API: (), .success, .error, .warning, .info, .loading, .custom, .promise, .update, .dismiss | | Toast | Compound wrapper for toast.customToast.Icon / Body / Title / Description / Action / Cancel / CloseButton | | useToaster() | Headless hook — { toasts, dismiss, update } | | useToastItemContext() | Read the current toast inside a custom render | | ensureToastStyles() | Inject keyframes manually (auto-called by <Toaster>) |

<Toaster /> props

| Prop | Type | Default | Description | |---|---|---|---| | id | string | — | Scope the Toaster — only toasts with a matching toasterId are rendered. Omit for the default catch-all instance. | | position | "top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right" | "bottom-right" | Default position when a toast doesn't override it | | visibleCount | number | 3 | How many toasts are visible before older ones collapse | | duration | number | 4000 | Default auto-dismiss in ms (Infinity disables) | | closable | boolean | false | Always show the close button on every toast | | expand | boolean | true | Expand collapsed stack on hover | | gap | number | 8 | Pixel offset between collapsed toasts | | width | number | 360 | Toast width (px) | | pauseOnBlur | boolean | true | Pause timers while the window loses focus | | render | (toast, dismiss) => ReactNode | — | Override the default per-toast rendering | | className | string | — | Class on each per-position container | | container | HTMLElement \| null | document.body | Custom portal target |

toast(...) API

| Call | Returns | Description | |---|---|---| | toast(title, opts?) | id | Default toast | | toast.success(title, opts?) | id | ✓ icon | | toast.error(title, opts?) | id | ✕ icon, role="alert" | | toast.warning(title, opts?) | id | ⚠ icon, role="alert" | | toast.info(title, opts?) | id | ℹ icon | | toast.loading(title, opts?) | id | spinner, duration: Infinity by default | | toast.custom(render, opts?) | id | Render function returning custom JSX | | toast.promise(p, msgs, opts?) | id | Loading → success / error transition | | toast.update(id, patch) | boolean | Update an existing toast | | toast.dismiss(id?) | void | Dismiss one (or all when id omitted) |

ToastOptions

| Prop | Type | Description | |---|---|---| | id | string \| number | Stable id for update / dismiss | | description | ReactNode | Secondary line below title | | action | { label, onClick } | Inline call-to-action button | | cancel | { label, onClick } | Outline cancel button | | duration | number | Override default auto-dismiss (ms) | | position | ToastPosition | Override default position for this toast | | closable | boolean | Force a close button on this toast | | toasterId | string | Route this toast to a specific <Toaster id="..." /> instance | | icon | ReactNode | Override the variant icon | | className / style | — | Per-toast styling overrides | | onDismiss(id) | (id) => void | Fires whenever the toast leaves | | onAutoClose(id) | (id) => void | Fires only when the timer dismisses (not user) |

Recipes

All variants

toast("Saved successfully");
toast.success("Profile updated");
toast.error("Couldn't save changes");
toast.warning("Storage almost full");
toast.info("New version available");
toast.loading("Uploading…");

Action button (Undo)

toast("Item moved to trash", {
  action: { label: "Undo", onClick: () => restore() },
  duration: 6000,
});

Destructive confirmation

toast.error("Delete this draft?", {
  description: "This action cannot be undone.",
  action: { label: "Delete", onClick: () => deleteDraft() },
  cancel: { label: "Cancel", onClick: () => {} },
  duration: Infinity,
});

Promise pattern

toast.promise(saveDocument(), {
  loading: "Saving…",
  success: (data) => `Saved ${data.filename}`,
  error: (err) => `Failed: ${err.message}`,
});

Update by id

const id = toast.loading("Uploading…", { duration: Infinity });
await upload();
toast.success("Done!", { id, duration: 3000 });

Custom compound layout

import { Toast, toast } from "eglador-ui-react-toast";

toast.custom((t) => (
  <Toast toast={t} dismiss={() => toast.dismiss(t.id)}>
    <Toast.Body>
      <Toast.Title>New message</Toast.Title>
      <Toast.Description>"Can we ship this on Friday?"</Toast.Description>
    </Toast.Body>
    <Toast.Action onClick={() => openReply()}>Reply</Toast.Action>
    <Toast.CloseButton />
  </Toast>
));

Free-form custom JSX

toast.custom(() => (
  <div className="w-full p-3 rounded-sm border border-zinc-200 bg-zinc-900 text-white">
    Anything you want — no row chrome.
  </div>
));

Headless hook

import { useToaster } from "eglador-ui-react-toast";

function NotificationCenter() {
  const { toasts, dismiss } = useToaster();
  return (
    <ul>
      {toasts.map((t) => (
        <li key={t.id}>
          {t.title} <button onClick={() => dismiss(t.id)}>×</button>
        </li>
      ))}
    </ul>
  );
}

Render-prop on <Toaster>

<Toaster
  render={(t, dismiss) => (
    <div className="my-toast-shell">
      <span>{t.title}</span>
      <button onClick={dismiss}>×</button>
    </div>
  )}
/>

Persistent toast

toast("Persistent message", {
  duration: Infinity,
  closable: true,
});

Scoped Toasters (multi-instance)

Mount more than one <Toaster> and route specific toasts to specific instances by id:

// Two scoped toasters — e.g. one inside a modal, one global:
<Toaster id="modal" position="top-center" />
<Toaster position="bottom-right" />  {/* default catch-all */}

toast.success("Saved");                                  // → bottom-right
toast.error("Form invalid", { toasterId: "modal" });     // → top-center

The default <Toaster> (no id) catches every toast that doesn't supply a toasterId. Toasters with an id only render toasts whose toasterId matches.

Compatibility

Works with any React-based framework: Next.js, Remix, Vite + React, Gatsby.

<Toaster /> is marked "use client" (it uses useState / useEffect and the DOM). Place it inside a client component / after a "use client" directive. The store itself is module-level — call toast(...) from event handlers and effects, not server components.

Development

npm install
npm run dev               # tsup watch mode
npm run build             # production build to dist/
npm run typecheck         # tsc --noEmit
npm run storybook         # Storybook dev (http://localhost:6006)
npm run build-storybook   # static Storybook export

Publishing

Publishing is automated via GitHub Actions. When a GitHub Release is created, the package is published to npm.

  1. Update version in package.json
  2. Commit and push
  3. Create a GitHub Release with a matching tag (e.g. v1.0.0)

Author

Kenan Gündoğan — https://github.com/kenangundogan

Maintained under Eglador

License

MIT