Basic Toast | Community Components | 21st

Basic Toast

Here is Toast component

Vite + React + TS

Usage

"use client";

import { Toast, Toaster, createToaster } from "@ark-ui/react/toast";
import { Portal } from "@ark-ui/react/portal";
import { X, Loader2 } from "lucide-react";
import { useRef } from "react";

const toaster = createToaster({
  overlap: true,
  placement: "bottom-end",
  gap: 16,
});

export default function ToastUpdatable() {
  const toastId = useRef<string>();

const createToast = () => {
    toastId.current = toaster.create({
      title: "Uploading...",
      description: "Your file is being uploaded.",
      type: "loading",
      duration: Infinity,
    });
  };

const updateToast = () => {
    if (!toastId.current) return;

toaster.update(toastId.current, {
      title: "Upload complete!",
      description: "Your file has been uploaded successfully.",
      type: "success",
      duration: 5000,
    });
  };

return (
    <div className="bg-white dark:bg-gray-800 w-full px-4 py-12 rounded-xl flex flex-col items-center">
      <div className="flex gap-2">
        <button
          type="button"
          onClick={createToast}
          className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors text-sm font-medium"
        >
          Start Upload
        </button>
        <button
          type="button"
          onClick={updateToast}
          className="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors text-sm font-medium"
        >
          Complete Upload
        </button>
      </div>

<Portal>
        <Toaster toaster={toaster}>
          {(toast) => (
            <Toast.Root className="bg-linear-to-r from-white to-gray-50 border-l-4 border-blue-500 rounded-lg shadow-lg min-w-80 p-4 relative overflow-anywhere transition-all duration-300 ease-default will-change-transform h-(--height) opacity-(--opacity) translate-x-(--x) translate-y-(--y) scale-(--scale) z-(--z-index)">
              <div className="flex items-start gap-3">
                {toast.type === "loading" && (
                  <Loader2 className="w-4 h-4 mt-0.5 text-blue-500 animate-spin shrink-0" />
                )}
                {toast.type === "success" && (
                  <div className="w-4 h-4 mt-0.5 bg-green-500 rounded-full flex items-center justify-center shrink-0">
                    <X className="w-2 h-2 text-white rotate-45" />
                  </div>
                )}
                <div className="flex-1">
                  <Toast.Title className="text-gray-900 font-semibold text-sm">
                    {toast.title}
                  </Toast.Title>
                  <Toast.Description className="text-gray-600 text-sm mt-1">
                    {toast.description}
                  </Toast.Description>
                  {toast.type === "loading" && (
                    <div className="mt-2 w-full bg-gray-200 rounded-full h-1.5">
                      <div
                        className="bg-blue-500 h-1.5 rounded-full animate-pulse"
                        style={{ width: "60%" }}
                      ></div>
                    </div>
                  )}
                </div>
              </div>
              <Toast.CloseTrigger className="absolute top-3 right-3 p-1 hover:bg-white/50 rounded transition-colors text-gray-400 hover:text-gray-600">
                <X className="w-3 h-3" />
              </Toast.CloseTrigger>
            </Toast.Root>
          )}
        </Toaster>
      </Portal>
    </div>
  );
}

More examples

Toast Variants

Vite + React + TS

"use client";

import { Toast, Toaster, createToaster } from "@ark-ui/react/toast";
import { Portal } from "@ark-ui/react/portal";
import { CheckCircle, AlertCircle, X, Info, AlertTriangle } from "lucide-react";

const toaster = createToaster({
  overlap: true,
  placement: "bottom-end",
  gap: 16,
});

const toastTypes = [
  {
    type: "success" as const,
    title: "Success!",
    description: "Your changes have been saved.",
    icon: CheckCircle,
    colors: "bg-green-50 border-l-4 border-green-500 text-green-800",
    iconColor: "text-green-500",
  },
  {
    type: "error" as const,
    title: "Error occurred",
    description: "Something went wrong. Please try again.",
    icon: AlertCircle,
    colors: "bg-red-50 border-l-4 border-red-500 text-red-800",
    iconColor: "text-red-500",
  },
  {
    type: "warning" as const,
    title: "Warning",
    description: "This action cannot be undone.",
    icon: AlertTriangle,
    colors: "bg-yellow-50 border-l-4 border-yellow-500 text-yellow-800",
    iconColor: "text-yellow-500",
  },
  {
    type: "info" as const,
    title: "New update available",
    description: "Version 2.1.0 is now available for download.",
    icon: Info,
    colors: "bg-blue-50 border-l-4 border-blue-500 text-blue-800",
    iconColor: "text-blue-500",
  },
];

export default function ToastTypes() {
  return (
    <div className="bg-white dark:bg-gray-800 w-full px-4 py-12 rounded-xl flex flex-col items-center">
      <div className="flex flex-wrap gap-2 justify-center">
        {toastTypes.map((item) => (
          <button
            key={item.type}
            type="button"
            onClick={() =>
              toaster.create({
                title: item.title,
                description: item.description,
                type: item.type,
              })
            }
            className="px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors text-xs font-medium capitalize"
          >
            {item.type}
          </button>
        ))}
      </div>

<Portal>
        <Toaster toaster={toaster}>
          {(toast) => {
            const toastConfig = toastTypes.find((t) => t.type === toast.type);
            const Icon = toastConfig?.icon || Info;

return (
              <Toast.Root
                className={`rounded-lg shadow-lg min-w-80 p-4 relative overflow-anywhere transition-all duration-300 ease-default will-change-transform h-(--height) opacity-(--opacity) translate-x-(--x) translate-y-(--y) scale-(--scale) z-(--z-index) ${
                  toastConfig?.colors || "bg-white"
                }`}
              >
                <div className="flex items-start gap-3">
                  <Icon
                    className={`w-4 h-4 mt-0.5 shrink-0 ${
                      toastConfig?.iconColor || "text-gray-500"
                    }`}
                  />
                  <div className="flex-1">
                    <Toast.Title className="font-semibold text-sm">
                      {toast.title}
                    </Toast.Title>
                    <Toast.Description className="text-sm opacity-80">
                      {toast.description}
                    </Toast.Description>
                  </div>
                </div>
                <Toast.CloseTrigger className="absolute top-3 right-3 p-1 hover:bg-black/10 rounded transition-colors">
                  <X className="w-3 h-3" />
                </Toast.CloseTrigger>
              </Toast.Root>
            );
          }}
        </Toaster>
      </Portal>
    </div>
  );
}

Dependencies

Source