bloomtpl
  • Documentation
  • Contact
bloomtpl

The best Next.js templates for modern web applications.
Deploy faster, code less.

Your Feedback

Review us onTrustpilot

Templates

  • All Templates10+
  • Business Templates
  • E-Commerce Templates
  • Landing Page Templates
  • Blog Templates
  • Portfolio Templates
  • Free Templates

Resources

  • Docs
  • Contact
  • Blog
  • License

Follow us

Twitter

Legal

  • Privacy Policy
  • Refund Policy
  • Terms of Use
  • Legal Notice
© 2025 BloomTPL™. All Rights Reserved.
❤️Made with love for developers
ComponentsNotification Card

Notification Card

Interactive notification system with auto-dismiss, actions, expandable content, and multiple types.

ReactTailwind CSSTypeScriptResponsiveCard

Live Preview

Interactive
Notification Card.tsxLive Code

How to use

  1. 1. Copy the component code
  2. 2. Paste it into your React/Next.js project
  3. 3. Make sure you have Tailwind CSS configured
  4. 4. Customize colors, spacing, and content as needed

Dependencies

react
tailwindcss
Back to Components

Need complete templates?

View Premium Templates
"use client";

import { useState } from "react";

type NotificationType = "success" | "warning" | "error" | "info";

interface Notification {
  id: string;
  type: NotificationType;
  title: string;
  message: string;
  timestamp: Date;
  actions?: {
    label: string;
    action: () => void;
    variant?: "primary" | "secondary";
  }[];
  autoDismiss?: boolean;
  dismissTime?: number;
}

export default function NotificationCard() {
  const [notifications, setNotifications] = useState<Notification[]>([]);
  const [isExpanded, setIsExpanded] = useState<Record<string, boolean>>({});

  const sampleNotifications: Omit<Notification, "id" | "timestamp">[] = [
    {
      type: "success",
      title: "Payment Successful",
      message:
        "Your payment of $299.99 has been processed successfully. You will receive a confirmation email shortly.",
      autoDismiss: true,
      dismissTime: 5000,
      actions: [
        {
          label: "View Receipt",
          action: () => console.log("View receipt"),
          variant: "primary",
        },
        { label: "Dismiss", action: () => {}, variant: "secondary" },
      ],
    },
    {
      type: "warning",
      title: "Storage Almost Full",
      message:
        "Your account storage is 85% full. Consider upgrading your plan or removing old files.",
      actions: [
        {
          label: "Upgrade Plan",
          action: () => console.log("Upgrade"),
          variant: "primary",
        },
        {
          label: "Manage Files",
          action: () => console.log("Manage"),
          variant: "secondary",
        },
      ],
    },
    {
      type: "error",
      title: "Connection Failed",
      message:
        "Unable to connect to the server. Please check your internet connection and try again.",
      actions: [
        {
          label: "Retry",
          action: () => console.log("Retry"),
          variant: "primary",
        },
      ],
    },
    {
      type: "info",
      title: "New Feature Available",
      message:
        "We have released a new dashboard feature that helps you track your analytics better. Check it out!",
      actions: [
        {
          label: "Learn More",
          action: () => console.log("Learn more"),
          variant: "primary",
        },
        { label: "Later", action: () => {}, variant: "secondary" },
      ],
    },
  ];

  const addNotification = () => {
    const sample =
      sampleNotifications[
        Math.floor(Math.random() * sampleNotifications.length)
      ];
    const newNotification: Notification = {
      ...sample,
      id: Date.now().toString(),
      timestamp: new Date(),
    };

    setNotifications((prev) => [newNotification, ...prev]);

    if (newNotification.autoDismiss && newNotification.dismissTime) {
      setTimeout(() => {
        dismissNotification(newNotification.id);
      }, newNotification.dismissTime);
    }
  };

  const dismissNotification = (id: string) => {
    setNotifications((prev) => prev.filter((notif) => notif.id !== id));
    setIsExpanded((prev) => {
      const newExpanded = { ...prev };
      delete newExpanded[id];
      return newExpanded;
    });
  };

  const toggleExpanded = (id: string) => {
    setIsExpanded((prev) => ({
      ...prev,
      [id]: !prev[id],
    }));
  };

  const clearAll = () => {
    setNotifications([]);
    setIsExpanded({});
  };

  const getNotificationStyles = (type: NotificationType) => {
    const styles = {
      success: {
        border: "border-green-200",
        bg: "bg-green-50",
        icon: "text-green-600",
        title: "text-green-900",
        message: "text-green-800",
        progress: "bg-green-600",
      },
      warning: {
        border: "border-yellow-200",
        bg: "bg-yellow-50",
        icon: "text-yellow-600",
        title: "text-yellow-900",
        message: "text-yellow-800",
        progress: "bg-yellow-600",
      },
      error: {
        border: "border-red-200",
        bg: "bg-red-50",
        icon: "text-red-600",
        title: "text-red-900",
        message: "text-red-800",
        progress: "bg-red-600",
      },
      info: {
        border: "border-blue-200",
        bg: "bg-blue-50",
        icon: "text-blue-600",
        title: "text-blue-900",
        message: "text-blue-800",
        progress: "bg-blue-600",
      },
    };
    return styles[type];
  };

  const getNotificationIcon = (type: NotificationType) => {
    const icons = {
      success: (
        <svg
          className="w-5 h-5"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth={2}
            d="M5 13l4 4L19 7"
          \/>
        <\/svg>
      ),
      warning: (
        <svg
          className="w-5 h-5"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth={2}
            d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"
          \/>
        <\/svg>
      ),
      error: (
        <svg
          className="w-5 h-5"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth={2}
            d="M6 18L18 6M6 6l12 12"
          \/>
        <\/svg>
      ),
      info: (
        <svg
          className="w-5 h-5"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth={2}
            d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
          \/>
        <\/svg>
      ),
    };
    return icons[type];
  };

  const formatTimestamp = (timestamp: Date) => {
    const now = new Date();
    const diffInMinutes = Math.floor(
      (now.getTime() - timestamp.getTime()) \/ 60000
    );

    if (diffInMinutes < 1) return "Just now";
    if (diffInMinutes < 60) return `${diffInMinutes}m ago`;

    const diffInHours = Math.floor(diffInMinutes \/ 60);
    if (diffInHours < 24) return `${diffInHours}h ago`;

    const diffInDays = Math.floor(diffInHours \/ 24);
    return `${diffInDays}d ago`;
  };

  return (
    <div className="w-full max-w-md mx-auto space-y-4">
      <div className="flex flex-col sm:flex-row gap-2 p-4 bg-gray-50 rounded-lg">
        <button
          onClick={addNotification}
          className="flex-1 bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition"
        >
          <span className="hidden sm:inline">Add Random Notification<\/span>
          <span className="sm:hidden">Add Notification<\/span>
        <\/button>
        {notifications.length > 0 && (
          <button
            onClick={clearAll}
            className="flex-1 sm:flex-none bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition"
          >
            Clear All ({notifications.length})
          <\/button>
        )}
      <\/div>

      <div className="space-y-3 max-h-[500px] overflow-y-auto">
        {notifications.length === 0 ? (
          <div className="text-center py-8 text-gray-500">
            <div className="w-12 h-12 mx-auto mb-3 bg-gray-100 rounded-full flex items-center justify-center">
              <svg
                className="w-6 h-6"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M15 17h5l-5 5v-5z"
                \/>
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M9 12l2 2 4-4"
                \/>
              <\/svg>
            <\/div>
            <p className="text-sm">No notifications<\/p>
            <p className="text-xs text-gray-400 mt-1">
              <span className="hidden sm:inline">
                Click &quot;Add Random Notification&quot; to see examples
              <\/span>
              <span className="sm:hidden">
                Add a notification to get started
              <\/span>
            <\/p>
          <\/div>
        ) : (
          notifications.map((notification) => {
            const styles = getNotificationStyles(notification.type);
            const expanded = isExpanded[notification.id];
            const shouldTruncate = notification.message.length > 80;

            return (
              <div
                key={notification.id}
                className={`${styles.border} ${styles.bg} border rounded-lg shadow-sm overflow-hidden transition-all duration-300`}
              >
                {notification.autoDismiss && notification.dismissTime && (
                  <div className="h-1 bg-gray-200">
                    <div
                      className={`h-full ${styles.progress} transition-all ease-linear`}
                      style={{
                        animation: `shrink ${notification.dismissTime}ms linear forwards`,
                      }}
                    \/>
                  <\/div>
                )}

                <div className="p-4">
                  <div className="flex items-start gap-3">
                    <div className={`flex-shrink-0 ${styles.icon}`}>
                      {getNotificationIcon(notification.type)}
                    <\/div>

                    <div className="flex-1 min-w-0">
                      <div className="flex items-start justify-between gap-2">
                        <h4 className={`font-semibold text-sm ${styles.title}`}>
                          {notification.title}
                        <\/h4>
                        <div className="flex items-center gap-1 flex-shrink-0">
                          <span className="text-xs text-gray-500">
                            {formatTimestamp(notification.timestamp)}
                          <\/span>
                          <button
                            onClick={() => dismissNotification(notification.id)}
                            className="text-gray-400 hover:text-gray-600 p-1 rounded transition"
                          >
                            <svg
                              className="w-4 h-4"
                              fill="none"
                              stroke="currentColor"
                              viewBox="0 0 24 24"
                            >
                              <path
                                strokeLinecap="round"
                                strokeLinejoin="round"
                                strokeWidth={2}
                                d="M6 18L18 6M6 6l12 12"
                              \/>
                            <\/svg>
                          <\/button>
                        <\/div>
                      <\/div>

                      <div className={`mt-1 text-sm ${styles.message}`}>
                        {shouldTruncate && !expanded ? (
                          <div>
                            {notification.message.substring(0, 80)}...
                            <button
                              onClick={() => toggleExpanded(notification.id)}
                              className="ml-2 text-gray-600 hover:text-gray-800 font-medium underline"
                            >
                              Show more
                            <\/button>
                          <\/div>
                        ) : (
                          <div>
                            {notification.message}
                            {shouldTruncate && expanded && (
                              <button
                                onClick={() => toggleExpanded(notification.id)}
                                className="ml-2 text-gray-600 hover:text-gray-800 font-medium underline"
                              >
                                Show less
                              <\/button>
                            )}
                          <\/div>
                        )}
                      <\/div>

                      {notification.actions &&
                        notification.actions.length > 0 && (
                          <div className="flex flex-wrap gap-2 mt-3">
                            {notification.actions.map((action, index) => (
                              <button
                                key={index}
                                onClick={() => {
                                  action.action();
                                  if (
                                    action.label === "Dismiss" ||
                                    action.label === "Later"
                                  ) {
                                    dismissNotification(notification.id);
                                  }
                                }}
                                className={`px-3 py-1 rounded text-sm font-medium transition ${
                                  action.variant === "primary"
                                    ? `bg-${
                                        notification.type === "warning"
                                          ? "yellow"
                                          : notification.type === "error"
                                          ? "red"
                                          : notification.type === "info"
                                          ? "blue"
                                          : "green"
                                      }-600 text-white hover:bg-${
                                        notification.type === "warning"
                                          ? "yellow"
                                          : notification.type === "error"
                                          ? "red"
                                          : notification.type === "info"
                                          ? "blue"
                                          : "green"
                                      }-700`
                                    : "bg-gray-200 text-gray-700 hover:bg-gray-300"
                                }`}
                              >
                                <span className="hidden sm:inline">
                                  {action.label}
                                <\/span>
                                <span className="sm:hidden">
                                  {action.label === "View Receipt"
                                    ? "View"
                                    : action.label === "Upgrade Plan"
                                    ? "Upgrade"
                                    : action.label === "Manage Files"
                                    ? "Manage"
                                    : action.label === "Learn More"
                                    ? "Learn"
                                    : action.label}
                                <\/span>
                              <\/button>
                            ))}
                          <\/div>
                        )}
                    <\/div>
                  <\/div>
                <\/div>
              <\/div>
            );
          })
        )}
      <\/div>

      <div className="text-center text-xs text-gray-500 p-3 bg-gray-50 rounded-lg">
        <p>
          <span className="hidden sm:inline">
            Interactive notification system with auto-dismiss, actions, and
            responsive design
          <\/span>
          <span className="sm:hidden">
            Interactive notifications with auto-dismiss
          <\/span>
        <\/p>
      <\/div>

      <style jsx>{`
        @keyframes shrink {
          from {
            width: 100%;
          }
          to {
            width: 0%;
          }
        }
      `}<\/style>
    <\/div>
  );
}