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

toaststar

v0.1.6

Published

Cinematic React toast notifications with center-launch animations, stacked cards, and optional IndexedDB history.

Downloads

292

Readme

toaststar

toaststar is a React toast library with a cinematic center-launch intro, hover fan-out stacks, queue-aware delivery, and optional persisted or in-memory history.

Features

  • Center launch animation before settling to the top or bottom edge
  • Hover-only stack fan-out instead of always-expanded cards
  • Scoped controllers so multiple widgets or apps on one page do not cross-fire
  • toast.loading(), toast.update(), and toast.promise() for async flows
  • Opt-in progress bars, swipe-to-dismiss, dedupe keys, queue limits, and overflow policies
  • Custom React component bodies inside the default toast shell
  • Theme presets plus per-provider and per-toast appearance overrides
  • Action buttons, lifecycle callbacks, and optional headless mode
  • Optional IndexedDB or memory-backed history plus a ready-made ToastHistoryPanel

Install

npm install toaststar

Quick start

import { ToastProvider, createToastScope } from "toaststar";

const appToast = createToastScope("marketing-site");

function SaveButton() {
  return (
    <button
      type="button"
      onClick={() => {
        void appToast.promise(saveProfile(), {
          loading: {
            title: "Saving profile",
            description: "Waiting for the API to finish.",
          },
          success: {
            title: "Profile saved",
            description: "Your latest settings are live.",
          },
          error: (error) => ({
            title: "Save failed",
            description:
              error instanceof Error ? error.message : "Please try again.",
          }),
        });
      }}
    >
      Save
    </button>
  );
}

export default function App() {
  return (
    <ToastProvider
      scope="marketing-site"
      position="top"
      defaultTheme="glass"
      maxVisible={3}
      queueLimit={8}
      overflowStrategy="queue"
      dedupeBehavior="update"
      showProgress={false}
      swipeToDismiss
      history={{ enabled: true, storage: "indexeddb", limit: 30 }}
      appearance={{
        radius: 28,
        border: "1px solid rgba(255,255,255,0.24)",
      }}
    >
      <SaveButton />
    </ToastProvider>
  );
}

Imperative API

import { createToastScope, toast } from "toaststar";

const appToast = createToastScope("dashboard");

toast.show("Quick title-only toast");

appToast.info({
  title: "New message",
  description: "Alice: the deploy preview is ready.",
});

const loadingId = appToast.loading({
  title: "Uploading assets",
  description: "Keeping a single toast alive while work runs.",
  showProgress: true,
});

appToast.update(loadingId, {
  title: "Upload finished",
  intent: "success",
  loading: false,
  persistent: false,
});

void appToast.promise(fetch("/api/publish"), {
  loading: {
    title: "Publishing release",
    showProgress: true,
  },
  success: { title: "Release published" },
  error: (error) => ({
    title: "Publish failed",
    description:
      error instanceof Error ? error.message : "Unknown error",
  }),
});

appToast.dismiss(loadingId);
appToast.clear();

Use the default toast singleton when your page has a single provider. Use createToastScope("your-app") when multiple apps, widgets, or micro-frontends might coexist on the same origin.

Scoped runtimes

import { ToastProvider, createToastScope } from "toaststar";

const checkoutToast = createToastScope("checkout");
const profileToast = createToastScope("profile");

function App() {
  return (
    <>
      <ToastProvider scope="checkout" />
      <ToastProvider scope="profile" />
    </>
  );
}

checkoutToast.success("Checkout only");
profileToast.success("Profile only");

ToastProvider can be self-closing when you only need the toast layer.

Custom component body

function ReleaseStatusCard() {
  return (
    <div className="grid gap-3">
      <strong>Design system release</strong>
      <div className="h-2 overflow-hidden rounded-full bg-slate-200">
        <span className="block h-full w-[82%] rounded-full bg-blue-500" />
      </div>
      <div className="grid grid-cols-2 gap-2">
        <button type="button">Inspect build</button>
        <button type="button">Acknowledge</button>
      </div>
    </div>
  );
}

toast.show({
  title: "Design system release",
  body: <ReleaseStatusCard />,
  icon: "✨",
});

body replaces the default title/description/action layout while keeping the stack behavior, close button, theming, and history metadata.

Provider options

<ToastProvider
  position="top"
  defaultDuration={4500}
  introDuration={420}
  exitDuration={220}
  defaultTheme="midnight"
  maxCollapsed={4}
  maxVisible={3}
  burstMaxVisible={6}
  burstWindow={320}
  queueLimit={8}
  overflowStrategy="queue"
  dedupeBehavior="update"
  showProgress={false}
  gap={14}
  edgeOffset={28}
  expandedOffset={18}
  expandOnHover
  pauseOnHover
  swipeToDismiss
  portalTarget={false}
  history={{ enabled: true, storage: "indexeddb", limit: 50 }}
  onToastOpen={(toast) => console.log("open", toast.id)}
  onToastClose={(toast, reason) => console.log("close", toast.id, reason)}
  onToastAction={(toast) => console.log("action", toast.id)}
/>

React hooks

import {
  useToast,
  useToastActions,
  useToastHistory,
  useToastState,
} from "toaststar";

function SaveButton() {
  const { success } = useToastActions();

  return (
    <button type="button" onClick={() => success("Saved")}>
      Save
    </button>
  );
}

Use useToastActions() when a component only triggers toasts. It avoids subscribing that component to live toast/history state updates.

History reuse and API sync

import { useToastHistory } from "toaststar";

function NotificationSync() {
  const {
    exportHistory,
    fetchHistory,
    history,
    importHistory,
    postHistory,
  } = useToastHistory();

  async function backupHistory() {
    await postHistory("/api/toast-history", { method: "POST" });
  }

  async function restoreHistory() {
    await fetchHistory("/api/toast-history", undefined, "replace");
  }

  async function hydrateFromLocalCache() {
    const snapshot = exportHistory();
    await importHistory(snapshot, "merge");
  }

  return (
    <div>
      <button type="button" onClick={backupHistory}>
        Backup {history.length} items
      </button>
      <button type="button" onClick={restoreHistory}>
        Restore from API
      </button>
      <button type="button" onClick={hydrateFromLocalCache}>
        Reuse current snapshot
      </button>
    </div>
  );
}

exportHistory() returns a reusable snapshot object with items, namespace, databaseName, and storage. postHistory() sends that snapshot as JSON to your API. fetchHistory() accepts API responses shaped as either a raw array of history items, { items: [...] }, or { history: [...] }, then persists the result back into the configured history storage.

Dedupe and queue control

toast.show({
  title: "Sync batch started",
  dedupeKey: "sync-batch",
});

toast.show({
  title: "Sync batch started",
  description: "Updates the existing toast instead of creating another one.",
  dedupeKey: "sync-batch",
});

Use dedupeBehavior="ignore" | "update" | "reset-duration" on the provider to decide how repeated keys are handled.

Use overflowStrategy="queue" | "drop-oldest" | "drop-newest" with maxVisible and queueLimit to control bursts.

Finite maxVisible values get a small burst headroom by default so rapid clicks can momentarily overshoot the steady-state cap before normal queueing resumes. Set burstMaxVisible to tune or disable that headroom, and adjust burstWindow in milliseconds to control how long a burst stays active.

Feature toggles

  • Progress bars are off by default. Enable them globally with <ToastProvider showProgress /> or per toast with showProgress: true.
  • Queueing is off until you set a finite maxVisible. Leave it unset for unlimited visible toasts.
  • Dedupe is off by default because dedupeBehavior defaults to "ignore".
  • Swipe dismiss is on by default. Turn it off with swipeToDismiss={false}.
  • Hover fan-out and pause behavior are toggled with expandOnHover and pauseOnHover.
  • History is off until history is enabled.
  • Set history.storage to "memory" to disable IndexedDB while keeping history inside the current tab session.
  • Leave history.namespace unset unless you need a custom value. toaststar now derives a unique default namespace per scope/site.
  • The built-in layer is on by default. Turn it off with headless.
  • Custom portals are optional. Pass portalTarget={false} to render inline instead of portaling.

Lifecycle callbacks

toast.show({
  title: "Archive ready",
  action: {
    label: "Undo",
    onClick: restoreRow,
  },
  onOpen: (id) => analytics.track("toast_open", { id }),
  onAction: (id) => analytics.track("toast_action", { id }),
  onClose: (id, reason) =>
    analytics.track("toast_close", { id, reason }),
});

Headless mode

<ToastProvider headless portalTarget={false}>
  <AppShell />
</ToastProvider>

headless keeps the runtime, command bus, history, and hooks active while skipping the built-in toast layer.

Optional history panel

import { ToastHistoryPanel } from "toaststar";

function NotificationsPage() {
  return (
    <ToastHistoryPanel
      title="Recent notifications"
      theme="midnight"
      maxItems={8}
    />
  );
}

Local demo

npm --prefix demo install
npm run demo

The demo app lives in demo/ and includes homepage-style usage examples for async flows, queue bursts, dedupe, history, and appearance tuning.

Themes

  • glass
  • midnight
  • sunset
  • forest
  • ocean

Notes

  • ToastProvider should be mounted once near your app root.
  • History only persists when history.enabled is true and the browser supports IndexedDB.
  • portalTarget defaults to document.body. Pass false to disable the portal.
  • The package targets React 18 and newer.

Community