TrueSpec

Toast Notification System

Custom toast component, queue management, animations, and accessibility

What You’ll Build

After following this guide, you will have a working implementation of toast notification system in your project. Build a lightweight toast notification system or use the battle-tested sonner library. Toasts stack from the bottom, auto-dismiss, support different types (success, error, info), and are accessible with proper ARIA roles.

Use Cases & Problems Solved

  • Implement interactive UI features that users expect from modern apps
  • Follow established patterns that scale and remain maintainable
  • Reduce boilerplate and avoid common frontend pitfalls

Prerequisites

  • React 18+

Step-by-Step Implementation

The following snippet shows how to using sonner (recommended). Copy this into your project and adjust the values for your environment.

npm install sonner

Setup and usage

The following snippet shows how to setup and usage. Copy this into your project and adjust the values for your environment.

// app/layout.tsx or App.tsx
import { Toaster, toast } from 'sonner';

function App() {
  return (
    <>
      <Toaster position="bottom-right" richColors expand />
      <button onClick={() => toast.success('Profile saved!')}>Save</button>
      <button onClick={() => toast.error('Failed to delete')}>Delete</button>
      <button onClick={() => toast.promise(saveData(), {
        loading: 'Saving...',
        success: 'Data saved!',
        error: 'Could not save',
      })}>Save with Promise</button>
    </>
  );
}

// Use from anywhere — even outside React components
import { toast } from 'sonner';

async function handleUpload(file) {
  try {
    await uploadFile(file);
    toast.success('File uploaded successfully');
  } catch (err) {
    toast.error(\`Upload failed: \${err.message}\`);
  }
}

⚠️ Don’t Do This

❌ Using alert() for user feedback

// Blocks the UI thread! User can't interact until dismissed
alert('Profile saved successfully!');
alert('Error: Could not delete user');

✅ Use non-blocking toast notifications

toast.success('Profile saved!');
tost.error('Could not delete user');
// User can continue working while notification shows

Testing

Verify your implementation with these tests:

// __tests__/toast-notification-system.test.ts
import { describe, it, expect } from 'vitest';

describe('Toast Notification System', () => {
  it('should initialize without errors', () => {
    // Test that the setup completes successfully
    expect(() => setup()).not.toThrow();
  });

  it('should handle the primary use case', async () => {
    const result = await execute();
    expect(result).toBeDefined();
    expect(result.success).toBe(true);
  });

  it('should handle edge cases', async () => {
    // Test with empty/null input
    const result = await execute(null);
    expect(result.error).toBeDefined();
  });
});

Verification

# Click each button type (success, error, promise)
# Verify toast appears in bottom-right corner
# Verify auto-dismiss after ~4 seconds
# Verify multiple toasts stack properly
# Test keyboard accessibility: toast should be aria-live

Related Specs

Beginner

Zustand State Management

Store setup, slices, persist middleware, devtools, and TypeScript

Frontend Patterns
Intermediate

In-App Notification Center

Bell icon, unread count, real-time updates, and mark-as-read

Email & Notifications
Intermediate

Next.js Server Actions Guide

Form mutations, revalidation, optimistic updates, and error handling

Frontend Patterns