Skip to content

FRD-036: Toast Queue Manager

FieldValue
OwnerDavid Holmes
StatusDraft
Last Updated2026-05-26
RelatedADR-027 (Default Tech Stack)
Target Releasev2.0.0
TypeWidget
SizeS
PriorityP1 — SaaS Widget

Document Summary

Build a Toast Queue Manager widget that provides a programmatic toast queue with dismiss-all action, per-toast action support, configurable stacking behavior, and auto-dismiss with configurable duration. It enhances the existing toast.tsx primitive and toast.store.ts with queue management, stacking limits, and a persistent toast history panel.


Introduction

Overview

The design system provides a toast.tsx primitive with react-toastify integration and a Zustand-based toast store. The current implementation supports showing toasts with tones (success, error, warning, info) and auto-dismiss. However, it lacks queue management (limiting concurrent toasts), a dismiss-all action, per-toast action buttons (e.g., “Undo”, “View”), stacking behavior configuration, and a toast history panel. SaaS applications with high-frequency events (deployments, CI runs, real-time collaboration) need these capabilities.

Goals

  • Provide a ToastQueueManager widget that wraps the existing toast system with queue management.
  • Support configurable max concurrent toasts with overflow queuing.
  • Add a “Dismiss all” action when multiple toasts are visible.
  • Support per-toast action buttons (primary and secondary actions).
  • Configure stacking behavior (stack, replace, or queue).
  • Ship with Storybook stories demonstrating all behaviors.

Non-Goals

  • Replacing the existing toast primitive or react-toastify integration.
  • Building a notification center (persistent notification list with read/unread state).
  • Push notification integration.

Scope

In Scope

ItemDescription
Queue managementConfigurable max concurrent toasts; overflow toasts are queued and shown as slots open.
Dismiss allA “Dismiss all” button appears when 2+ toasts are visible.
Per-toast actionsToasts can include a primary action button (e.g., “Undo”) and optional secondary action.
Stacking behaviorConfigurable: “stack” (all visible up to max), “replace” (new toast replaces oldest), “queue” (FIFO).
Auto-dismissConfigurable duration per toast; persistent toasts (no auto-dismiss) supported.
Toast counterWhen queued toasts exceed max, show “N more” indicator.
Stories and testsStorybook stories for all behaviors; unit tests for queue logic.

Out of Scope

ItemReason
Notification centerDifferent UX pattern with persistence, read/unread state, and filtering.
Push notificationsBrowser notification API is a separate concern.
Toast positioningAlready handled by react-toastify; not changing.

Users and Pain Points

UserPain Point
SaaS developersNo way to limit concurrent toasts; rapid events flood the screen.
CI/CD dashboard teamsDeployment toasts stack infinitely; users cannot dismiss them all at once.
E-commerce teamsToast for “Item added to cart” needs an “Undo” action button.
Accessibility-focused teamsToo many simultaneous toasts overwhelm screen readers.

Definitions

TermDefinition
Toast queueAn ordered list of pending toasts waiting to be displayed when a slot opens.
Max concurrentThe maximum number of toasts visible simultaneously.
Stacking behaviorHow new toasts interact with existing ones: stack (add), replace (swap oldest), or queue (wait).
Persistent toastA toast that does not auto-dismiss; requires manual dismissal.
Toast actionA button within a toast that triggers a callback (e.g., “Undo”, “View details”).

Current State

  • toast.tsx (src/components/ui/toast.tsx): Toast component using react-toastify with tone variants (success, error, warning, info). Supports auto-dismiss with DEFAULT_TOAST_DURATION (5000ms). Uses a Zustand store for toast state.
  • toast.store.ts (src/stores/toast.store.ts): Re-exports toast utilities from toast.tsx.
  • ToastContainer: react-toastify container with custom transition (motion-toast-enter, motion-toast-exit).
  • Current limitations: No max concurrent limit, no dismiss-all, no per-toast actions, no queue management, no stacking configuration.

Proposed Solution

Enhanced Toast API

Extend the existing toast() function to accept action buttons:

toast({
title: "Item added to cart",
tone: "success",
action: { label: "Undo", onClick: () => undoAddToCart() },
secondaryAction: { label: "View cart", onClick: () => navigateToCart() },
persistent: false,
duration: 5000,
});

ToastQueueManager Widget

Build src/components/widgets/toast-queue-manager.tsx as a wrapper around ToastContainer that adds:

  1. Queue logic: When toast count exceeds maxConcurrent (default 3), new toasts enter a FIFO queue. As visible toasts dismiss, queued toasts are promoted.
  2. Dismiss all: When 2+ toasts are visible, a “Dismiss all” button renders above the toast stack.
  3. Counter badge: When queued toasts exist, a “+N more” indicator shows below the stack.
  4. Stacking config: behavior prop controls how new toasts interact with the stack.

Implementation Strategy

Rather than replacing react-toastify, the queue manager sits above it:

  • Intercept toast() calls via the Zustand store.
  • Track visible vs. queued toasts.
  • Show/dismiss via react-toastify’s API.
  • Render the dismiss-all button and counter as siblings to ToastContainer.

Requirements

Requirement Priorities

  • Must Have: Per-toast action buttons, configurable auto-dismiss duration.
  • Should Have: Max concurrent limit with queue, dismiss-all action.
  • Could Have: Toast counter badge, stacking behavior configuration, toast history panel.

Functional Requirements

IDRequirementPriority
FR-01toast() function accepts action prop with { label, onClick } for a primary action button.Must
FR-02toast() function accepts secondaryAction for an optional second action button.Should
FR-03toast() function accepts persistent: true to disable auto-dismiss.Must
FR-04toast() function accepts duration to override the default auto-dismiss timer.Must
FR-05ToastQueueManager limits concurrent visible toasts to maxConcurrent (default 3).Should
FR-06Toasts exceeding maxConcurrent enter a FIFO queue and display when a slot opens.Should
FR-07”Dismiss all” button appears when 2+ toasts are visible.Should
FR-08”+N more” indicator appears when queued toasts exist.Could
FR-09behavior prop configures stacking: “stack” (default), “replace”, “queue”.Could
FR-10Each toast can be individually dismissed via close button.Must (existing)
FR-11Dismiss-all clears both visible and queued toasts.Should

Non-Functional Requirements

IDRequirementTarget
NFR-01Toast render latency< 50ms from toast() call to visible render.
NFR-02Queue processing latency< 100ms from slot opening to queued toast appearing.
NFR-03Bundle size increase< 2 KB gzipped over existing toast implementation.
NFR-04MemoryQueue capped at 50 toasts; oldest queued toasts are dropped beyond cap.
NFR-05Dark modeFull token-based dark mode support (already exists in toast.tsx).

API/Interface Requirements

Enhanced toast() function

interface ToastAction {
label: string;
onClick: () => void;
}
interface ToastOptions {
title: string;
description?: string;
tone?: "success" | "error" | "warning" | "info";
action?: ToastAction;
secondaryAction?: ToastAction;
persistent?: boolean;
duration?: number; // ms, default 5000
}
function toast(options: ToastOptions): string; // returns toast ID
function dismissToast(id: string): void;
function dismissAllToasts(): void;

ToastQueueManager widget

interface ToastQueueManagerProps {
maxConcurrent?: number; // default 3
behavior?: "stack" | "replace" | "queue"; // default "stack"
position?: "top-right" | "top-center" | "bottom-right" | "bottom-center";
className?: string;
}

Accessibility Requirements

IDRequirement
A11Y-01Toasts use role="status" with aria-live="polite" (existing behavior).
A11Y-02Error toasts use role="alert" with aria-live="assertive".
A11Y-03Action buttons within toasts are keyboard accessible (Tab, Enter).
A11Y-04”Dismiss all” button has aria-label="Dismiss all notifications".
A11Y-05Queue counter announces “N more notifications pending” to screen readers.
A11Y-06Max 3 concurrent toasts to avoid overwhelming screen reader users.

Content and Documentation Requirements

IDRequirement
DOC-01Storybook docs page with usage guidelines, prop table, and interactive examples.
DOC-02Migration guide from existing toast() calls (backward compatible; new props are optional).
DOC-03Recipe showing toast with undo action for destructive operations.
DOC-04Guidelines on when to use persistent vs. auto-dismiss toasts.

Dependencies

DependencyTypeRisk
src/components/ui/toast.tsxInternalLow — enhancing existing component.
react-toastifyExistingLow — wrapping, not replacing.
zustandExistingLow — extending existing toast store.
src/styles/motion.cssInternalLow — existing toast animations.

Risks and Tradeoffs

RiskLikelihoodImpactMitigation
Queue logic adds complexity to the toast systemMediumMediumKeep queue manager as an optional wrapper; existing toast() works without it.
react-toastify’s internal queue conflicts with our queue logicLowHighUse react-toastify in manual mode (disable its internal queue); our manager controls visibility.
Per-toast actions increase toast height and visual densityLowLowActions render as compact text buttons; limit to 2 actions per toast.

Open Questions

#QuestionOwnerStatus
OQ-01Should the queue manager support a toast history panel (viewable after dismissal)?David HolmesOpen
OQ-02Should persistent toasts count toward the max concurrent limit?David HolmesOpen
OQ-03Should the “dismiss all” button also cancel queued toasts or only visible ones?David HolmesOpen

Acceptance Criteria

#Criterion
AC-01toast() accepts action and secondaryAction props; action buttons render inside the toast.
AC-02toast({ persistent: true }) creates a toast that does not auto-dismiss.
AC-03ToastQueueManager limits concurrent toasts to maxConcurrent.
AC-04Toasts exceeding the limit enter a queue and display when slots open.
AC-05”Dismiss all” appears when 2+ toasts are visible and clears all toasts.
AC-06Existing toast() calls continue to work without changes (backward compatible).
AC-07All components pass axe-core checks with zero violations.
AC-08Storybook stories exist for: basic toast with action, persistent toast, queue overflow, dismiss all.
AC-09pnpm typecheck and pnpm vitest run --project unit pass with zero errors.

LLM Handoff Instructions

When implementing this FRD:

  1. Start with the toast() API enhancement in src/components/ui/toast.tsx. Add action, secondaryAction, persistent, and duration to the toast options. Render action buttons inside the toast surface. This is backward compatible — all new props are optional.
  2. Then build ToastQueueManager in src/components/widgets/toast-queue-manager.tsx. It wraps ToastContainer and manages a queue via the Zustand toast store.
  3. Queue logic: Intercept toast creation in the store. If visible count >= maxConcurrent, push to queue array. On toast dismiss, shift from queue and display.
  4. Dismiss all: Render a small “Dismiss all” button positioned above the toast stack. It calls reactToastifyToast.dismiss() for all active IDs and clears the queue.
  5. Counter badge: Render “+N more” below the toast stack when queue is non-empty.
  6. Stories in src/components/widgets/toast-queue-manager.stories.tsx. Use interactive stories with buttons to trigger toasts and demonstrate queue behavior.
  7. Tests in src/components/widgets/toast-queue-manager.test.tsx. Test queue limit, dismiss-all, action button clicks, persistent toast.

Key files:

  • src/components/ui/toast.tsx — existing toast implementation.
  • src/stores/toast.store.ts — re-exports.
  • src/styles/motion.css — toast animation classes.

Decision Log

DateDecisionRationale
2026-05-26Enhance existing toast() API rather than creating a new function.Backward compatibility; consumers upgrade incrementally.
2026-05-26Queue manager is an optional wrapper, not a required replacement for ToastContainer.Teams that don’t need queue management keep their existing setup.
2026-05-26Default maxConcurrent is 3.Balances visibility with screen real estate and accessibility concerns.

Document History

VersionDateAuthorChanges
0.12026-05-26David HolmesInitial draft.