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

@opentui-ui/toast

v0.0.5

Published

A beautiful toast library for terminal UIs built on OpenTUI

Readme

Installation

bun add @opentui-ui/toast

Quick Start

import { toast, ToasterRenderable } from "@opentui-ui/toast";

// 1. Add the toaster to your app (one line!)
ctx.root.add(new ToasterRenderable(ctx));

// 2. Show toasts from anywhere!
toast("Hello World");

That's it! No providers, no context, no configuration required.

Quick Reference

// Toast Types
toast("message");           // default
toast.success("message");   // green checkmark
toast.error("message");     // red X
toast.warning("message");   // yellow warning
toast.info("message");      // blue info
toast.loading("message");   // animated spinner

// Common Patterns
toast("msg", { description: "details" }); // Two-line toast
toast("msg", { duration: Infinity });     // Persistent (manual dismiss)
toast("msg", { action: { label: "Undo", onClick: fn } }); // With button

// Dismiss
const id = toast("Hello");
toast.dismiss(id);  // Dismiss one
toast.dismiss();    // Dismiss all

// Update existing toast
const id = toast.loading("Uploading...");
toast.success("Done!", { id }); // Updates in place

Toast Types

toast("Default notification");
toast.success("Operation completed!");
toast.error("Something went wrong");
toast.warning("Please check your input");
toast.info("Did you know?");
toast.loading("Processing...");

With Descriptions

toast.success("File uploaded", {
  description: "Your file has been saved to the cloud",
});

Promise Toast

The toast.promise() API automatically shows loading, success, and error states:

toast.promise(fetchData(), {
  loading: "Fetching data...",
  success: "Data loaded successfully!",
  error: "Failed to load data",
});

// With dynamic messages
toast.promise(saveUser(data), {
  loading: "Saving user...",
  success: (user) => `${user.name} has been saved`,
  error: (err) => `Error: ${err.message}`,
});

Actions

Add interactive buttons to your toasts:

toast("File deleted", {
  action: {
    label: "Undo",
    onClick: () => restoreFile(),
  },
});

Updating Toasts

Update an existing toast by passing its ID:

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

// Later...
toast.success("Upload complete!", { id });

// Or on error
toast.error("Upload failed", { id });

Dismissing Toasts

// Dismiss a specific toast
const id = toast("Hello");
toast.dismiss(id);

// Dismiss all toasts
toast.dismiss();

Duration

Duration Presets

Use the built-in TOAST_DURATION presets for consistent, readable duration values:

import { toast, TOAST_DURATION } from "@opentui-ui/toast";

// Quick confirmation (2s)
toast.success("Copied!", { duration: TOAST_DURATION.SHORT });

// Standard duration (4s) - this is the default
toast("Hello", { duration: TOAST_DURATION.DEFAULT });

// Important message (6s)
toast.warning("Check your settings", { duration: TOAST_DURATION.LONG });

// Critical information (10s)
toast.error("Connection lost", { duration: TOAST_DURATION.EXTENDED });

// Manual dismiss only
toast.info("Click to continue", { duration: TOAST_DURATION.PERSISTENT });

Duration Preset Values

| Preset | Duration | Use Case | | ------------ | -------- | ------------------------- | | SHORT | 2000ms | Brief confirmations | | DEFAULT | 4000ms | Standard notifications | | LONG | 6000ms | Important messages | | EXTENDED | 10000ms | Critical information | | PERSISTENT | Infinity | Requires manual dismissal |

Custom Duration

You can also pass any number in milliseconds:

// Custom duration (in milliseconds)
toast("This disappears in 10 seconds", {
  duration: 10000,
});

// Persistent toast (won't auto-dismiss)
toast("I'll stay until dismissed", {
  duration: Infinity,
});

Themes

Optional theme presets are available via a separate import. These override the built-in defaults with alternative visual styles.

import { ToasterRenderable } from "@opentui-ui/toast";
import { minimal } from "@opentui-ui/toast/themes";

const toaster = new ToasterRenderable(ctx, minimal);

Available Themes

| Theme | Description | | ------------ | --------------------------------- | | minimal | Clean and unobtrusive, no borders | | monochrome | Grayscale only, no colors |

Customizing Themes

Spread a theme and override specific options:

import { minimal } from "@opentui-ui/toast/themes";

const toaster = new ToasterRenderable(ctx, {
  ...minimal,
  position: "bottom-right",
  stackingMode: "stack",
});

Theme Utilities

import { themes } from "@opentui-ui/toast/themes";

// Access all themes
themes.minimal;
themes.monochrome;

Theme Types

import type { ToasterTheme } from "@opentui-ui/toast/themes";

Toaster Configuration

Customize the toaster appearance and behavior:

const toaster = new ToasterRenderable(ctx, {
  // Position on screen
  position: "bottom-right", // 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'

  // Gap between toasts (terminal rows)
  gap: 1,

  // How to handle multiple toasts
  stackingMode: "single", // 'single' | 'stack'

  // Max visible toasts in stack mode
  visibleToasts: 3,

  // Show close button on toasts
  closeButton: false,

  // Maximum width for toasts
  maxWidth: 60,

  // Offset from screen edges
  offset: {
    top: 1,
    right: 2,
    bottom: 1,
    left: 2,
  },

  // Custom icons
  icons: {
    success: "✓",
    error: "✗",
    warning: "⚠",
    info: "ℹ",
    loading: "◌",
    close: "×",
  },

  // Toast styling and duration options
  toastOptions: {
    style: {
      /* base styles */
    },
    duration: 4000,
    success: {
      style: {
        /* overrides */
      },
      duration: 3000,
    },
    // ... other types
  },
});

ToasterOptions Reference

| Option | Type | Default | Description | | --------------- | ------------------------------ | ------------------------------------------ | ------------------------------------------------------------ | | position | Position | "bottom-right" | Position on screen | | gap | number | 1 | Gap between toasts (terminal rows) | | stackingMode | StackingMode | "single" | How to handle multiple toasts: "single" or "stack" | | visibleToasts | number | 3 | Max visible toasts in stack mode | | closeButton | boolean | false | Show close button on toasts | | maxWidth | number | 60 | Maximum width for toasts (terminal columns) | | offset | ToasterOffset | { top: 1, right: 2, bottom: 1, left: 2 } | Offset from screen edges | | icons | Partial<ToastIcons> \| false | - | Custom icons for each toast type, or false to disable | | toastOptions | ToastOptions | - | Default toast options (styles, duration, per-type overrides) |

Styling

Configure toast styles using the toastOptions prop:

const toaster = new ToasterRenderable(ctx, {
  toastOptions: {
    // Base styles applied to all toasts
    style: {
      backgroundColor: "#1a1a1a",
      foregroundColor: "#ffffff",
      borderColor: "#333333",
      borderStyle: "rounded", // 'single' | 'double' | 'rounded' | 'heavy'
      paddingX: 1,
      paddingY: 0,
    },
    // Default duration for all toasts
    duration: 4000,
    // Per-type overrides
    success: {
      style: { borderColor: "#22c55e" },
      duration: 3000,
    },
    error: {
      style: { borderColor: "#ef4444" },
      duration: 6000,
    },
    warning: {
      style: { borderColor: "#f59e0b" },
    },
    info: {
      style: { borderColor: "#3b82f6" },
    },
    loading: {
      style: { borderColor: "#6b7280" },
    },
  },
});

ToastStyle Reference

| Property | Type | Default | Description | | ------------------- | -------------------------- | ----------- | ------------------------------------------------------------------------------------------- | | border | boolean \| BorderSides[] | true | Border configuration. true = all sides, false = none, or array like ["left", "right"] | | borderColor | string | "#333333" | Border color (hex, rgb, or named) | | borderStyle | BorderStyle | "single" | Border style: "single" | "double" | "rounded" | "heavy" | | customBorderChars | BorderCharacters | - | Custom border characters (overrides borderStyle) | | minHeight | number | 3 | Minimum height in terminal rows | | maxWidth | number | - | Maximum width in terminal columns | | minWidth | number | - | Minimum width in terminal columns | | padding | number | - | Uniform padding (all sides) | | paddingX | number | 1 | Horizontal padding (left + right) | | paddingY | number | 0 | Vertical padding (top + bottom) | | paddingTop | number | - | Top padding | | paddingBottom | number | - | Bottom padding | | paddingLeft | number | - | Left padding | | paddingRight | number | - | Right padding | | backgroundColor | string | "#1a1a1a" | Background color | | foregroundColor | string | "#ffffff" | Text/foreground color | | mutedColor | string | "#6b7280" | Muted text color (for descriptions) | | iconColor | string | - | Icon color (defaults to borderColor) |

Custom Border Characters

For full control over border rendering, use customBorderChars to define each border character:

const toaster = new ToasterRenderable(ctx, {
  toastOptions: {
    style: {
      border: ["left", "right"],
      customBorderChars: {
        topLeft: "",
        topRight: "",
        bottomLeft: "",
        bottomRight: "",
        horizontal: " ",
        vertical: "┃",
        topT: "",
        bottomT: "",
        leftT: "",
        rightT: "",
        cross: "",
      },
    },
  },
});

This is useful for creating unique border styles, like a vertical bar accent:

// Vertical bar on left and right only
border: ["left", "right"],
customBorderChars: {
  vertical: "┃",
  // Other characters can be empty strings
  topLeft: "", topRight: "", bottomLeft: "", bottomRight: "",
  horizontal: " ", topT: "", bottomT: "", leftT: "", rightT: "", cross: "",
},

Per-Toast Styles

Override styles on individual toasts:

toast.success("Custom styled!", {
  style: {
    borderColor: "#8b5cf6",
    backgroundColor: "#1e1b4b",
  },
});

Icon Sets

Choose from built-in icon sets based on terminal capabilities:

import {
  DEFAULT_ICONS, // Unicode icons (default)
  ASCII_ICONS, // ASCII-only for limited terminals
  MINIMAL_ICONS, // Single character icons
  EMOJI_ICONS, // Emoji icons
} from "@opentui-ui/toast";

// Use ASCII icons for terminals with limited Unicode support
const toaster = new ToasterRenderable(ctx, {
  icons: ASCII_ICONS,
});

// Use emoji icons for terminals with good emoji support
const toaster = new ToasterRenderable(ctx, {
  icons: EMOJI_ICONS,
});

Custom Loading Spinner

The loading icon can be either a static string or an animated spinner configuration:

const toaster = new ToasterRenderable(ctx, {
  icons: {
    // Static loading icon (no animation)
    loading: "...",

    // Or animated spinner
    loading: {
      frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
      interval: 80,
    },
  },
});

Some spinner examples:

// Dots spinner
{ frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"], interval: 80 }

// Circle spinner (default)
{ frames: ["◜", "◠", "◝", "◞", "◡", "◟"], interval: 100 }

// Simple ASCII spinner
{ frames: ["-", "\\", "|", "/"], interval: 100 }

// Bouncing bar
{ frames: ["[    ]", "[=   ]", "[==  ]", "[=== ]", "[ ===]", "[  ==]", "[   =]", "[    ]"], interval: 120 }

Disabling Icons

To disable icons entirely, set icons: false:

const toaster = new ToasterRenderable(ctx, {
  icons: false,
});

Individual toasts can still override this by providing a custom icon:

// Icons are disabled globally, but this toast will show a custom icon
toast.success("Done!", { icon: "✓" });

API Reference

toast(message, options?)

Show a default toast.

toast.success(message, options?)

Show a success toast with a checkmark icon.

toast.error(message, options?)

Show an error toast with an X icon.

toast.warning(message, options?)

Show a warning toast with a warning icon.

toast.info(message, options?)

Show an info toast with an info icon.

toast.loading(message, options?)

Show a loading toast with an animated spinner.

toast.promise(promise, options)

Show a toast that updates based on promise state.

toast.dismiss(id?)

Dismiss a specific toast by ID, or all toasts if no ID provided.

toast.getToasts()

Get all currently active toasts.

toast.getHistory()

Get all toasts ever created (including dismissed).

Toast Options

| Option | Type | Default | Description | | ------------- | -------------------------- | ---------- | ---------------------------------- | | id | string \| number | auto | Unique identifier for the toast | | description | string \| (() => string) | - | Secondary text below the title | | duration | number | 4000 | Time in ms before auto-dismiss | | dismissible | boolean | true | Whether the toast can be dismissed | | icon | string | type-based | Custom icon to display | | action | { label, onClick } | - | Action button configuration | | closeButton | boolean | false | Show close button | | style | ToastStyle | - | Per-toast style overrides | | onDismiss | (toast) => void | - | Callback when dismissed | | onAutoClose | (toast) => void | - | Callback when auto-closed |

Examples

Basic Example

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

const renderer = await createCliRenderer();

// Add toaster
const toaster = new ToasterRenderable(renderer, {
  position: "bottom-right",
});
renderer.root.add(toaster);

// Show some toasts
toast.success("Application started!");

setTimeout(() => {
  toast.info("Press 'q' to quit");
}, 1000);

Async Operation

async function saveData(data: unknown) {
  toast.promise(
    fetch("/api/save", {
      method: "POST",
      body: JSON.stringify(data),
    }),
    {
      loading: "Saving...",
      success: "Saved!",
      error: "Failed to save",
    }
  );
}

Manual Loading State

async function uploadFile(file: File) {
  const id = toast.loading("Uploading...");

  try {
    const result = await upload(file);
    toast.success(`Uploaded ${result.filename}`, { id });
  } catch (error) {
    toast.error("Upload failed", { id });
  }
}

React

For React applications, use the Toaster component and useToasts hook:

import { Toaster, useToasts, toast } from "@opentui-ui/toast/react";

function App() {
  return (
    <>
      <Toaster position="bottom-right" />
      <MyComponent />
    </>
  );
}

function MyComponent() {
  const { toasts } = useToasts();

  useKeyboard((key) => {
    if (key.name === "1") {
      toast.success("Hello World");
    }
  });

  return (
    <box>
      <text>Active toasts: {toasts.length}</text>
    </box>
  );
}

The useToasts hook provides reactive access to the current toast state, re-rendering your component whenever toasts are added, updated, or dismissed.

Solid

For Solid applications, use the Toaster component and useToasts hook:

import { Toaster, useToasts, toast } from "@opentui-ui/toast/solid";

function App() {
  return (
    <>
      <Toaster position="bottom-right" />
      <MyComponent />
    </>
  );
}

function MyComponent() {
  const toasts = useToasts();

  useKeyboard((key) => {
    if (key.name === "1") {
      toast.success("Hello World");
    }
  });

  return (
    <box>
      <text>Active toasts: {toasts().length}</text>
    </box>
  );
}

The useToasts hook returns a reactive accessor that updates whenever toasts change. Call it as a function (toasts()) to access the current array.

TypeScript

Full TypeScript support with exported types:

import type {
  Action,           // Action button configuration
  ExternalToast,    // Options for toast() calls
  Position,         // Toaster position type
  PromiseData,      // Configuration for toast.promise()
  SpinnerConfig,    // Animated spinner configuration { frames, interval }
  StackingMode,     // Stacking mode ('single' | 'stack')
  ToasterOffset,    // Offset configuration for positioning
  ToasterOptions,   // Configuration for ToasterRenderable
  ToastIcons,       // Custom icon set type
  ToastOptions,     // Default toast options (styles, duration, per-type overrides)
  ToastStyle,       // Per-toast styling options
  ToastType,        // Toast type variants
  TypeToastOptions, // Per-type options (style + duration)
} from "@opentui-ui/toast";

// Border types (for customBorderChars) come from @opentui/core
import type { BorderCharacters, BorderSides, BorderStyle } from "@opentui/core";

// Type guards
import { isAction, isSpinnerConfig } from "@opentui-ui/toast";

Constants

import { TOAST_DURATION } from "@opentui-ui/toast";

// Duration presets
TOAST_DURATION.SHORT;      // 2000ms - brief confirmations
TOAST_DURATION.DEFAULT;    // 4000ms - standard notifications
TOAST_DURATION.LONG;       // 6000ms - important messages
TOAST_DURATION.EXTENDED;   // 10000ms - critical information
TOAST_DURATION.PERSISTENT; // Infinity - manual dismiss only

Acknowledgments

Inspired by Sonner by Emil Kowalski.

License

MIT