React + MotionToasts

Toast animado

Una notificación que entra con muelle desde el borde que elijas y se va sola a los pocos segundos.

Ajustes

3s

Código

import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { CheckCircle2 } from "lucide-react";

const positionClasses = {
  "bottom-center": "bottom-4 left-1/2 -translate-x-1/2",
  "bottom-right": "bottom-4 right-4",
  "top-center": "top-4 left-1/2 -translate-x-1/2",
  "top-right": "top-4 right-4",
} as const;

interface ToastProps {
  position?: keyof typeof positionClasses;
  duration?: number;
  color?: string;
  title?: string;
  message?: string;
}

export default function Toast({
  position = "bottom-center",
  duration = 3,
  color = "#0071e3",
  title = "Guardado correctamente",
  message = "Tus cambios ya están aplicados.",
}: ToastProps) {
  const [items, setItems] = useState<{ id: number }[]>([]);
  const fromTop = position.startsWith("top");

  const fire = () => {
    const id = Date.now();
    setItems((p) => [...p, { id }]);
    setTimeout(() => setItems((p) => p.filter((i) => i.id !== id)), duration * 1000);
  };

  return (
    <div className="relative flex h-72 w-full items-center justify-center">
      <button
        type="button"
        onClick={fire}
        className="rounded-full px-5 py-2.5 text-sm font-semibold text-white"
        style={{ backgroundColor: color }}
      >
        Disparar notificación
      </button>
      <div
        className={`absolute z-10 flex w-64 max-w-[calc(100%-2rem)] flex-col gap-2 ${positionClasses[position]}`}
      >
        <AnimatePresence>
          {items.map((i) => (
            <motion.div
              key={i.id}
              role="status"
              initial={{ opacity: 0, y: fromTop ? -24 : 24, scale: 0.95 }}
              animate={{ opacity: 1, y: 0, scale: 1 }}
              exit={{ opacity: 0, y: fromTop ? -16 : 16, scale: 0.95 }}
              transition={{ type: "spring", stiffness: 380, damping: 28 }}
              className="flex w-full items-start gap-3 rounded-xl border border-black/10 bg-white p-3.5 text-[#1d1d1f] shadow-[0_4px_12px_rgba(0,0,0,0.04),0_24px_60px_rgba(0,0,0,0.1)] dark:border-white/10 dark:bg-[#1d1d1f] dark:text-[#f5f5f7]"
            >
              <CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0" style={{ color }} />
              <div>
                <p className="text-sm font-medium">{title}</p>
                <p className="mt-0.5 text-xs text-[#86868b]">{message}</p>
              </div>
            </motion.div>
          ))}
        </AnimatePresence>
      </div>
    </div>
  );
}

Prompt para IA

Pégalo en ChatGPT, Claude o Cursor y adaptará el bloque a tu proyecto con estos ajustes, aunque no uses React.

Más en Toasts

Dónde usarlo

Sirve para esos momentos de «hecho» dentro de una app: una factura guardada en un programa de facturación, un producto actualizado en el panel de una tienda, una foto de perfil cambiada. Es un toast de notificación en React que entra con un muelle suave, y si lanzas varios seguidos se colocan en fila en lugar de taparse.

Elige la posición según tu diseño: abajo a la derecha en un panel de escritorio, arriba al centro en el móvil, donde el pulgar tapa la parte de abajo. Con tres segundos basta para un mensaje corto; alarga la duración solo si el texto pide una segunda lectura.