ComponentToastNotificationFeedback

Toast Notification

A toast notification system with multiple variants (success, error, warning, info). Includes auto-dismiss and manual close options.

Preview

Code

'use client'

import { useState, useEffect, createContext, useContext, useCallback } from 'react'
import { X, CheckCircle, AlertCircle, AlertTriangle, Info } from 'lucide-react'
import { clsx } from 'clsx'

type ToastType = 'success' | 'error' | 'warning' | 'info'

interface Toast {
  id: string
  type: ToastType
  message: string
}

const icons: Record<ToastType, React.ReactNode> = {
  success: <CheckCircle className="size-5 text-green-500" />,
  error: <AlertCircle className="size-5 text-red-500" />,
  warning: <AlertTriangle className="size-5 text-amber-500" />,
  info: <Info className="size-5 text-blue-500" />,
}

const ToastContext = createContext<{
  toast: (type: ToastType, message: string) => void
} | null>(null)

export function ToastProvider({ children }: { children: React.ReactNode }) {
  const [toasts, setToasts] = useState<Toast[]>([])

  const toast = useCallback((type: ToastType, message: string) => {
    const id = crypto.randomUUID()
    setToasts((prev) => [...prev, { id, type, message }])
    setTimeout(() => {
      setToasts((prev) => prev.filter((t) => t.id !== id))
    }, 5000)
  }, [])

  const dismiss = (id: string) => {
    setToasts((prev) => prev.filter((t) => t.id !== id))
  }

  return (
    <ToastContext.Provider value={{ toast }}>
      {children}
      <div className="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
        {toasts.map((t) => (
          <div
            key={t.id}
            className={clsx(
              'flex items-center gap-3 rounded-lg bg-white px-4 py-3 shadow-lg',
              'dark:bg-olive-800 animate-in slide-in-from-right'
            )}
          >
            {icons[t.type]}
            <p className="text-sm text-olive-700 dark:text-olive-200">{t.message}</p>
            <button onClick={() => dismiss(t.id)} className="text-olive-400 hover:text-olive-600">
              <X className="size-4" />
            </button>
          </div>
        ))}
      </div>
    </ToastContext.Provider>
  )
}

export const useToast = () => {
  const context = useContext(ToastContext)
  if (!context) throw new Error('useToast must be used within ToastProvider')
  return context
}

Looking for more?

Browse the full collection of components or check out other exploration topics.