← React Compact Toast

Documentation

Everything the library does, on one page. The same reference lives in the README.

Install

npm install react-compact-toast

Mount the container once, near the root of your app, then call toast() from anywhere. The stylesheet is injected automatically; there is nothing else to import.

import { ToastContainer, toast } from 'react-compact-toast';

export default function App() {
  return (
    <>
      <button onClick={() => toast.success('Saved')}>Save</button>
      <ToastContainer />
    </>
  );
}

Showing toasts

toast('Copied to clipboard');
toast('Copied', { position: 'topRight', autoClose: 5000 });
toast({ text: 'Copied', icon: '📋' });

toast.success('Saved');
toast.error('Could not save');
toast.info('A new version is available');
toast.warning('Your session expires soon');
toast.loading('Uploading…'); // stays until you dismiss or update it

Every call returns an id you can act on later.

const id = toast.loading('Uploading…');
toast.update(id, { type: 'success', text: 'Uploaded', autoClose: 3000 });
toast.dismiss(id);  // play the exit animation, then remove
toast.remove(id);   // remove at once
toast.dismiss();    // all of them
toast.isActive(id); // true while it is on screen or queued

Passing your own id makes a toast idempotent, which is what you want for an action a user can repeat quickly.

const copy = () => {
  navigator.clipboard.writeText(link);
  toast('Link copied', { id: 'copy-link' }); // replaces itself, timer restarts
};

Promises

toast.promise(saveDraft(), {
  loading: 'Saving…',
  success: (draft) => `Saved as “${draft.title}”`,
  error: (err) => `Could not save: ${err.message}`,
});

The loading toast stays open until the promise settles, then turns into a success or error toast. The original promise is returned untouched, so rejections still reach your own catch.

Actions

toast('Message archived', {
  action: { label: 'Undo', onClick: () => restore() },
});

A toast with an action does not close on its own, so the action stays reachable. Call event.preventDefault() inside onClick to keep it open.

Toast options

toast(content, options?) takes a string, a React node, or an options object with a text key. The shorthands take the same arguments and set type.

Toast options
NameTypeDefaultDescription
idstringgeneratedReuse an id to update a toast in place instead of stacking duplicates.
textReactNode—The message.
type'success' | 'error' | 'info' | 'warning' | 'loading'—Adds a built-in icon and a data-rct-type hook.
iconReactNodefrom typeA custom icon. null removes it.
autoClosenumber | false3000Milliseconds before it closes. false (or 0) keeps it open.
closeOnClickbooleantrueClick, Enter, Space or Escape dismisses the toast.
pauseOnHoverbooleantrueHovering pauses the timer. Focus always does.
closeButtonbooleanautoShown automatically when nothing else can dismiss the toast.
closeButtonLabelstring'Close'Accessible name of that button.
action{ label, onClick }—A button inside the toast. Such a toast does not close on its own.
role'status' | 'alert''status''alert' interrupts the screen reader; type 'error' uses it by default.
positionToastPositioncontainer'stopLeft, topCenter, topRight, bottomLeft, bottomCenter, bottomRight.
classNamestring—Your classes. Replaces the built-in look, keeps the layout.
unstyledbooleanfalseDrops the layout too: only positioning and animation remain.
styleCSSProperties—Inline styles for the toast.
onClick(event) => void—Called when the toast itself is activated.
onClose() => void—Called when the toast leaves, for any reason.

highlightText, highlightColor, offset and containerStyle still work on a single toast but are deprecated and will be removed in 1.0.

ToastContainer

Container props
NameTypeDefaultDescription
positionToastPosition'bottomCenter'Default position for toasts that do not set one.
limitnumber6Toasts shown at once. Extra ones queue and appear as others leave.
toastOptionsPartial<ToastOptions>—Defaults merged under every toast.
newestOnTopbooleanfalseReverses the stacking order.
offsetnumber | string | { x, y }{ x: 20, y: 30 }Distance from the screen edges. Safe-area insets are added on top.
containerClassNamestring—Classes for each position group.
containerStyleCSSProperties—Inline styles for each position group.
labelstring'Notifications'Accessible name of the toast regions.
hotkeystring[]['altKey', 'KeyT']Focuses the newest toast. [] disables it.
pauseOnFocusLossbooleantruePauses timers while the tab is hidden or the window is blurred.
portalboolean | ElementfalseRenders toasts into document.body, or an element you pass.
injectStylesbooleantrueSet to false to import the stylesheet yourself.
noncestring—CSP nonce for the injected <style>.

Render exactly one container. Two would show every toast twice, and the second one warns in development.

Styling

Custom properties

Set them on :root, on a wrapper, or on the toast itself.

CSS custom properties
--rct-bg#282828
--rct-fg#fafafa
--rct-radius16px
--rct-padding16px 24px
--rct-min-width280px
--rct-max-width320px
--rct-min-height44px
--rct-font-size14px
--rct-shadow0 0 6px rgb(0 0 0 / 0.15)
--rct-action-bgrgb(128 128 128 / 0.2)
--rct-gap10px
--rct-gap-inline8px
--rct-offset-x20px
--rct-offset-y30px
--rct-z-index9999
--rct-enter-duration0.4s
--rct-exit-duration0.3s
--rct-success#22c55e
--rct-error#ef4444
--rct-info#3b82f6
--rct-warning#f59e0b
--rct-focus-ringcurrentColor

Selectors

Every part carries a data-rct-* attribute. The library's own rules are wrapped in :where(), so any single class of yours overrides them.

Styling selectors
[data-rct-container]One position group
[data-rct-toast]A toast
[data-rct-position="topRight"]On both, the resolved position
[data-rct-state="entering" | "exiting"]Animation state
[data-rct-type="success"]Semantic kind
[data-rct-styled]Present while the built-in look applies
[data-rct-base]Present unless unstyled
[data-rct-text]The message
[data-rct-icon]The icon slot
[data-rct-action]The action button
[data-rct-close]The close button

Your own classes

toast('Deployed', {
  className: 'rounded-xl bg-emerald-600 px-5 py-3 text-white shadow-lg',
});

className removes the built-in look but keeps the layout, so icon and text stay aligned. Add unstyled: true to remove the layout as well.

Tailwind CSS v4

Tailwind puts utilities in @layer utilities, and unlayered CSS always wins over layered CSS. Import the stylesheet into a layer so your utilities keep the upper hand.

@import 'tailwindcss';
@import 'react-compact-toast/styles.css' layer(components);
<ToastContainer injectStyles={false} />

Server rendering and Next.js

The package is a client module. Call toast() from client code only: an event handler, an effect, or a promise callback, never during render or on the server.

<ToastContainer /> may be rendered from a server layout as long as its props are serializable. This site does exactly that.

// app/layout.tsx — a server component
import { ToastContainer } from 'react-compact-toast';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        <ToastContainer position="topRight" />
      </body>
    </html>
  );
}

Under a strict Content Security Policy, pass nonce, or set injectStyles={false} and import react-compact-toast/styles.css yourself.

Accessibility

  • Toasts are announced through a live region that exists before the first toast, so nothing is missed. role: alert and type: error interrupt; everything else waits politely.
  • Auto-close pauses on hover, on focus, and while the tab is hidden, which is what WCAG 2.2.1 asks of a time limit.
  • A toast that cannot be dismissed any other way always gets a close button.
  • Enter and Space activate a toast, Escape dismisses it, and Alt + T jumps to the newest one. A dismissible toast carries aria-keyshortcuts="Escape" so the shortcut is announced, and the hotkey is ignored while you are typing in a field.
  • Under prefers-reduced-motion the slide is replaced by a fade.
  • Focus moves to the next toast, or back where it came from, when a focused toast disappears.

Listening to toasts

Every toast travels through eventManager, the publish–subscribe hub that lets toast() reach the container without a context or a provider. You can subscribe to it too — for analytics, logging, or a renderer of your own.

import { eventManager, ToastEvent } from 'react-compact-toast';

function log(id) {
  console.log('closed', id);
}

eventManager.on(ToastEvent.Delete, log);
const off = () => eventManager.off(ToastEvent.Delete, log);

Events are ToastEvent.Add, Dismiss, Delete and Update. The change is applied as soon as it is published, but callbacks run a tick later, so a subscriber never runs inside the publisher's stack. activeToastCount is read-only: it is derived from the toasts on screen rather than counted separately.

Headless usage

Build your own toast component on the same behaviour. Import from react-compact-toast/headless and the built-in component, its icons and the stylesheet stay out of your bundle — 3.1 kB gzipped instead of 7.0 kB.

import { useToast, useToastContainer } from 'react-compact-toast/headless';

function MyToast({ toast: record }) {
  const { toastProps, dismiss } = useToast(record.id);
  return (
    <div {...toastProps} className="my-toast">
      {record.text}
      <button onClick={dismiss}>Close</button>
    </div>
  );
}

function MyContainer() {
  const { groups } = useToastContainer({ limit: 3 });
  return Array.from(groups, ([position, toasts]) => (
    <div key={position} className={`my-group my-group--${position}`}>
      {toasts.map((record) => (
        <MyToast key={record.id} toast={record} />
      ))}
    </div>
  ));
}

toastProps carries the ref, ARIA attributes, keyboard handlers and hover tracking. Style the exit with [data-rct-state="exiting"] using a CSS animation or transition; the toast is removed once it finishes.