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

kanpai-toast

v0.1.0

Published

Dependency-free, accessible toast notifications. No framework required.

Readme

kanpai-toast

Toast notifications that get announced to screen readers, work from the keyboard, and don't leak timers. No framework, no dependencies.

I built this after watching yet another "accessible" toast library fall apart the moment you touch it with a keyboard. Most toast components on npm are a div with an opacity transition and a prayer. This one isn't. It came out of real GDPR/accessibility work at a company where that wasn't optional.

Install

npm install kanpai-toast
import toast from 'kanpai-toast';
import 'kanpai-toast/kanpai.css';

toast.success('Changes saved.');
toast.error('Could not save changes. Try again.');
toast('Custom message', { variant: 'info', duration: 8000 });

Written in plain JavaScript with JSDoc annotations. The build spits out TypeScript definitions (.d.ts) for autocomplete, but you don't need a TypeScript toolchain to use it.

Install (CSS)

The stylesheet drives everything visual off CSS custom properties, override them in :root to reskin it:

:root {
  --kanpai-bg: #1f1f1f;
  --kanpai-fg: #ffffff;
  --kanpai-radius: 6px;
  --kanpai-success-bg: #1a7f37;
  --kanpai-error-bg: #c1121f;
  --kanpai-warning-bg: #ffcc4d;
  --kanpai-warning-fg: #1f1f1f;
  --kanpai-gap: 0.5rem;
  --kanpai-z: 1000;
  --kanpai-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}

API

toast(message: string, options?: ToastOptions): string   // returns toast id
toast.success(message, options?)
toast.error(message, options?)
toast.warning(message, options?)
toast.info(message, options?)
toast.dismiss(id: string): void
toast.dismissAll(): void
toast.configure({ position }): void

ToastOptions:

| Option | Default | Notes | |---|---|---| | variant | 'info' | info | success | warning | error | | duration | 5000 | ms. 0 = stays until dismissed | | id | auto-generated | supply your own if you need to reference it later |

configure({ position }) accepts top-right | top-left | top-center | bottom-right | bottom-left | bottom-center.

Why this isn't just a div with opacity: 0

Two live regions, not one. info/success go into a polite region (role="status"); warning/error go into a separate assertive region (role="alert"). One region for everything means you're either interrupting people constantly or getting ignored. Screen readers announce alert immediately and status whenever the user's idle, and that split isn't cosmetic, it changes what actually gets heard.

aria-atomic="false". Set it to true and every new toast makes the screen reader re-read the entire region, old toasts included. false means each one announces once, on arrival, and shuts up after that.

Auto-dismiss pauses on hover and focus. Most implementations only listen for mouseenter. A keyboard or screen-reader user tabbing onto the close button never fires that event, so the toast can vanish mid-navigation. This version pauses on focusin/focusout too, and actually tracks remaining time instead of resetting the full duration every time you hover.

Escape dismisses the focused toast. This is table stakes for anything that acts like a notification. No excuse to skip it.

prefers-reduced-motion is respected. Instant removal for people who asked for it, not a fade they didn't.

Capped at 5 visible toasts, and eviction is instant. Fire 20 in a loop, a bad retry handler, a careless bulk action, and most libraries just stack them forever off-screen, quietly racking up live, orphaned timers. This one evicts the oldest immediately, no exit animation, so the cap is an actual guarantee and not "5, plus whatever's still fading out."

Timers actually get cleared. dismiss() clears the pending setTimeout before the element goes away, and the transitionend cleanup has a fallback timer for when transitionend never fires. An ancestor going display: none mid-transition will silently kill toasts forever otherwise.

No zombie timers from a dismiss-during-hover race. Dismiss a toast via the close button while the pointer's still over it, and the eventual mouseleave used to call resume() and schedule a dismiss timer for a toast that no longer exists. resume() now checks the toast's still active before it reschedules anything.

Global keyboard shortcut. Alt+T jumps focus straight to the most recent toast, so keyboard users don't have to hunt for it before it expires.

Focus restoration that's actually robust. Dismiss a focused toast and focus falls to the next open toast, then to a stack of prior focus targets, then to wherever you were before you ever touched a toast. Stale entries (an element that's since been removed from the DOM) get skipped rather than eaten silently. And when there's truly nowhere left to send focus, it releases it properly instead of calling .focus() on <body>, which is a no-op in every browser and would've quietly left focus stranded on an element mid-fade.

Hot module replacement (HMR) safe, all the way through. During development, both the live regions and the global Alt+T/focus-tracking listeners survive a module reload without duplicating. Reload the module 10 times over a dev session and you still get exactly one set of listeners and one set of regions, not 10.

High contrast mode works. Transparent borders keep toasts visible under Windows High Contrast / forced colors.

What's intentionally out of scope

  • No rich HTML in the message. textContent only. Toasts aren't the place for interactive content beyond a dismiss button; if you need actions, build a different component.
  • No stacking animation choreography. You get .kanpai--hide and transitions, bring your own motion library if you want more.
  • No framework wrapper. It's plain DOM. Drops into React/Vue/Svelte behind a thin wrapper around toast(). Wasn't going to ship React as a peer dependency for something this small. Open to adding wrappers if people actually want them.

Development

npm install
npm run test      # vitest + jsdom, 25 cases
npm run build     # bundle to dist/ (esm + cjs + .d.ts)
npm run serve     # local server at http://localhost:8080, opens demo/index.html

Tests cover: live-region routing by variant, auto-dismiss timing and custom durations, persistent (duration: 0) toasts, pause/resume on hover and keyboard focus, the dismiss-during-hover race above, Escape-to-dismiss, close-button behavior, the 5-toast eviction cap and its immediate (non-animated) removal, dismissAll, reduced-motion's instant removal, configure(), focus restoration (including a stale/disconnected focus-stack entry, and falling through to another open toast), and HMR safety for both live regions and global listeners.

License

MIT