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

headless-toast

v0.1.1

Published

A tiny, fully-accessible headless toast primitive for React. You bring the markup and styles; it handles state, timers, pause-on-hover and ARIA.

Readme

headless-toast

A tiny, fully-accessible headless toast primitive for React. You bring the markup and styles; it handles state, timers, pause-on-hover and ARIA.

tests coverage license npm version npm downloads bundle size

🌐 Live demo →

Most toast libraries ship a renderer, a portal, a theme and a pile of CSS you end up fighting. headless-toast ships none of that. It gives you the state machine — a toast store, auto-dismiss timers that pause on hover, in-place updates, and the correct ARIA attributes — and lets you render every pixel with your own components.

  • 🪶 Tiny — one small store, one hook, ~1.8 kB gzipped, zero dependencies.
  • Accessible by defaultrole="status"/"alert", aria-live, aria-atomic, all wired for you.
  • ⏯️ Pause on hover & focus — remaining time is preserved, never reset.
  • 🔁 Update in place — reuse an id to morph a loading toast into success.
  • 🎛️ Truly headless — no portal, no CSS, no opinions about how it looks.
  • 🔡 Fully typed — written in strict TypeScript, ships ESM + CJS + types.

For AI coding agents

Drop SKILL.md into your AI editor / Claude Code workspace and it learns how to use this library. Tells the agent when to reach for it, the install + canonical pattern, the public API, and the gotchas that are easy to miss.

Install

From GitHub (always works):

pnpm add github:kea0811/headless-toast

From npm (when published to npm):

pnpm add headless-toast

Using npm or yarn? Swap in npm install / yarn add — the package works the same.

react and react-dom (v18 or v19) are peer dependencies.

Quick start

Render a <Toaster> once near the root of your app, then call toast() from anywhere.

import { toast, useToaster } from 'headless-toast';

function Toaster() {
  const { toasts, getRegionProps, getToastProps, dismiss } = useToaster();

  return (
    <div {...getRegionProps()} className="toaster">
      {toasts.map((t) => (
        <div key={t.id} {...getToastProps(t)} className={`toast toast--${t.type}`}>
          <span>{t.message}</span>
          <button onClick={() => dismiss(t.id)} aria-label="Dismiss">
            ×
          </button>
        </div>
      ))}
    </div>
  );
}

function App() {
  return (
    <>
      <YourApp />
      <Toaster />
    </>
  );
}

// …then anywhere in your app:
toast.success('Saved!');
toast.error('Could not connect');

That's the whole integration. The library never renders anything — getRegionProps() and getToastProps() just hand you the right attributes and event handlers to spread.

A loading → success flow

Reuse an id and the toast updates in place instead of stacking — perfect for async work:

async function save() {
  const id = toast.loading('Saving changes…');
  try {
    await api.save();
    toast.success('All changes saved', { id });
  } catch {
    toast.error('Save failed', { id });
  }
}

API

toast

The imperative API. Every creator returns the toast's id.

toast(message, options?)          // a neutral toast (role="status")
toast.success(message, options?)  // polite success
toast.error(message, options?)    // assertive alert (role="alert")
toast.loading(message, options?)  // sticky until you swap or dismiss it

toast.dismiss(id?)  // hide one (or all) — fires your exit transition, then removes
toast.remove(id?)   // remove one (or all) immediately
toast.pause(id?)    // pause the auto-dismiss timer for one (or all)
toast.resume(id?)   // resume one (or all)

message is any ReactNode, so a string, JSX, or your own component all work.

ToastOptions

| Option | Type | Description | | ----------- | --------------------------- | --------------------------------------------------------------------- | | id | string | Reuse an id to update an existing toast instead of creating a new one. | | duration | number | Auto-dismiss delay in ms. Use Infinity to make it sticky. | | important | boolean | Force assertive (true) or polite (false) announcement. | | data | Record<string, unknown> | Arbitrary metadata carried on the toast and read back when rendering. |

useToaster(options?)

The hook that subscribes to the store and returns everything you need to render:

const {
  toasts,               // readonly Toast[] — includes toasts animating out (visible === false)
  prefersReducedMotion, // boolean — skip your transitions when true
  getRegionProps,       // () => props for the container (role, aria-label, hover/focus handlers)
  getToastProps,        // (toast) => props for one toast (role, aria-live, aria-atomic, data-*)
  dismiss, remove, pause, resume, // same helpers as on `toast`
} = useToaster({ label: 'Notifications' });

getToastProps(toast) returns data-type and data-visible attributes, so you can style by type ([data-type="error"]) and drive exit transitions ([data-visible="false"]) in pure CSS.

createToaster(options?)

Need more than one independent toast region, or want to avoid the shared default instance? Create your own:

import { createToaster } from 'headless-toast';

const { toast, useToaster, store } = createToaster({
  duration: 4000,    // default auto-dismiss for non-loading toasts
  removeDelay: 1000, // how long a dismissed toast lingers before removal (for exit anim)
  max: 3,            // cap simultaneous toasts; oldest are evicted (0 = no cap)
  label: 'Alerts',   // default aria-label for the live region
});

The default toast / useToaster exports are simply a createToaster() instance with the defaults above.

A note on accessibility

Toasts are announced via a live region. getToastProps gives success/default toasts role="status" + aria-live="polite", and error/important toasts role="alert" + aria-live="assertive", plus aria-atomic="true" so the whole message is read. Hover, focus and blur on the region pause and resume the timers, so a toast never vanishes while it's being read. You stay in control of focus order and visuals.

Contributing

Issues and PRs are welcome. To run the project locally:

pnpm install        # install deps (root + demo workspace)
pnpm test           # run the test suite
pnpm test:coverage  # run with coverage (100% across the board)
pnpm build          # build ESM + CJS + types
pnpm demo:dev       # run the demo app

License

MIT © kea0811