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

@relictombs/opentui-toast

v0.1.0

Published

Sonner-style animated toasts for OpenTUI

Downloads

39

Readme

@relictombs/opentui-toast

Sonner-style toast notifications for OpenTUI, animated by @relictombs/opentui-motion.

  • Mount one toaster, call toast() from anywhere
  • Success, info, warning, error, loading, and promise states
  • Update in place with stable IDs
  • Sonner-style layered stacks that fan out smoothly on hover
  • Stack-wide hover-to-pause, close buttons, and mouse actions
  • Six edge positions and independent toaster IDs
  • Bounded queues whose timers start only when a toast is visible
  • Core, Effect, React, and Solid entry points

Install

bun add @relictombs/opentui-toast @relictombs/opentui-motion @opentui/core

@relictombs/opentui-motion is a required peer dependency. Keeping one shared motion runtime lets toast transitions retarget safely without competing property owners or duplicated animation engines.

OpenTUI core

import { createCliRenderer } from "@opentui/core"
import { ToasterRenderable, toast } from "@relictombs/opentui-toast"

const renderer = await createCliRenderer({ exitOnCtrlC: true })

renderer.root.add(
  new ToasterRenderable(renderer, {
    placement: "bottom-right",
    visibleToasts: 3,
  }),
)

toast.success("Saved", {
  description: "Your changes are on disk",
})

The host is an overlay, but it does not claim the terminal hit grid outside visible toast cards. The rest of your app keeps receiving mouse input normally. Older cards collapse behind the newest toast; hover any part of the stack to fan it out and reveal every visible card.

Effect

Use @relictombs/opentui-toast/effect when toast settlement belongs to an Effect workflow. Ordinary operations report schema-backed Toast.OperationError failures, and promise toasts are interrupted with their owning scope.

import { Effect } from "effect"
import { ToastStore, ToasterRenderable } from "@relictombs/opentui-toast"
import { Toast } from "@relictombs/opentui-toast/effect"

const store = new ToastStore()
renderer.root.add(new ToasterRenderable(renderer, { store }))

const upload = Effect.scoped(
  Effect.gen(function* () {
    const notify = yield* Toast.make(store)
    const pending = yield* notify.promise(uploadRelease, {
      loading: "Uploading release",
      success: (release) => Effect.succeed(`Uploaded ${release.version}`),
      error: (cause) => Effect.succeed(`Upload failed: ${cause.message}`),
    })
    return yield* pending.await()
  }),
)

Closing the scope interrupts pending work and dismisses its loading toast. Exact record ownership prevents stale scope cleanup from removing or resurrecting a newer update with the same ID.

React

/** @jsxImportSource @opentui/react */

import { Toaster, toast } from "@relictombs/opentui-toast/react"

export function App() {
  return <Toaster placement="bottom-right" />
}

toast.success("Deployed")

Solid

/** @jsxImportSource @opentui/solid */

import { Toaster, toast } from "@relictombs/opentui-toast/solid"

export function App() {
  return <Toaster placement="bottom-right" />
}

toast.info("Listening on :3000")

Both adapters also export an explicit, idempotent registerToast() and the <toaster /> intrinsic for lower-level use.

API

The default call creates a toast and returns its ID:

const id = toast("Draft created")

toast.success("Saved")
toast.info("A new version is available")
toast.warning("Disk space is low")
toast.error("Deploy failed")
toast.loading("Uploading")

Pass an existing ID to update in place. Rapid updates retarget from the current visual state:

const id = toast.loading("Uploading release")

toast.success("Release uploaded", {
  id,
  description: "v1.4.0 is ready",
})

Or use the explicit update helper:

toast.update(id, {
  message: "Processing 42%",
  type: "info",
})

Dismiss one toast or all active toasts:

toast.dismiss(id)
toast.dismiss()

Promises

toast.promise keeps one ID through loading, success, and error states. Its stable handle exposes the original promise without creating an unhandled rejection for fire-and-forget UI use.

const upload = toast.promise(sendRelease(), {
  loading: "Uploading release",
  success: (release) => ({
    message: `Uploaded ${release.version}`,
    description: "Ready to deploy",
  }),
  error: (error) => `Upload failed: ${String(error)}`,
  finally: () => cleanup(),
})

const release = await upload.unwrap()
upload.dismiss()

A dismissed or superseded promise toast cannot reappear when stale work settles.

Actions

Actions run on primary mouse press and dismiss after a successful handler by default:

toast("File deleted", {
  action: {
    label: "Undo",
    onClick: async ({ preventDismiss }) => {
      await restoreFile()
      preventDismiss()
      toast.success("File restored")
    },
  },
  cancel: {
    label: "Dismiss",
    onClick: () => {},
  },
  onActionError: (error) => {
    toast.error(`Undo failed: ${String(error)}`)
  },
})

Hovering pauses only the dwell timer; enter, exit, and reflow animations remain responsive.

Placement and multiple hosts

The host default is configured with placement. An individual toast can override it with position:

new ToasterRenderable(renderer, {
  toasterId: "builds",
  placement: "top-right",
})

toast.error("Build failed", {
  toasterId: "builds",
  position: "bottom-center",
})

Supported positions are top-left, top-center, top-right, bottom-left, bottom-center, and bottom-right.

Isolated stores

The exported toast uses the default process-wide store for the simplest setup. Tests, embedded apps, and multiple renderer domains can create isolated APIs explicitly:

import { ToastStore, ToasterRenderable, createToast } from "@relictombs/opentui-toast"

const store = new ToastStore({ maxToasts: 50 })
const notify = createToast(store)
const toaster = new ToasterRenderable(renderer, { store })

renderer.root.add(toaster)
notify.success("Isolated")

Toaster options

| Option | Default | Purpose | | --------------- | -------------: | --------------------------------------------- | | placement | bottom-right | Default stack edge | | visibleToasts | 3 | Visible cards per position | | duration | 4000 | Dwell time in milliseconds | | gap | 1 | Rows between cards | | offset | 1 | Rows/columns from the viewport edge | | toastWidth | 42 | Preferred card width, clamped to the terminal | | expand | false | Keep stacks fanned out without hover | | closeButton | true | Show a mouse-dismiss button | | pauseOnHover | true | Pause every stack timer while hovered | | reducedMotion | false | Resolve visual transitions immediately |

Use duration: Infinity for persistent non-loading notifications. Loading toasts are persistent until updated or dismissed.

Design notes

The API and defaults are inspired by Sonner: one host, an imperative callable API, excellent defaults, stable ID updates, promise state transitions, and invisible edge-case handling. The renderer is OpenTUI-native—no DOM, hooks, browser-only globals, or motion/react bridge.

Development

From a repository checkout, install dependencies at the workspace root and run package commands from packages/opentui/toast:

cd packages/opentui/toast
bun run test
bun run check
bun run build
bun run test:packed

License

MIT