Toast
Notificaciones toast con Sonner - toast de éxito, error, carga y personalizados en shadcn/ui.
Busca en todas las páginas de la documentación
Notificaciones toast con Sonner - toast de éxito, error, carga y personalizados en shadcn/ui.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida - lista para copiar y pegar.
npx shadcn@latest add sonner// app/layout.tsx - añade el Toaster
import { Toaster } from "@/components/ui/sonner";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
);
}// Uso en cualquier lugar
import { toast } from "sonner";
toast("Notificación predeterminada");
toast.success("¡Guardado correctamente!");
toast.error("Algo salió mal");
toast.info("¿Sabías que...?");
toast.warning("Revisa tu entrada");
toast.loading("Procesando...");Cuándo usarlo: Cuando necesites retroalimentación no bloqueante para acciones del usuario - guardados, eliminaciones, errores y progreso de tareas en segundo plano.
"use client";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
export function ToastShowcase() {
// Toasts simples
function showSuccess() {
toast.success("Perfil actualizado", {
description: "Tus cambios se han guardado.",
});
}
function showError() {
toast.error("Error al guardar", {
description: "Comprueba tu conexión e inténtalo de nuevo.",
action: {
label: "Reintentar",
onClick: () => console.log("Retrying..."),
},
});
}
// Toast con promise - muestra carga, luego éxito o error
function handleSave() {
const savePromise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.3 ? resolve("done") : reject(new Error("fail"));
}, 2000);
});
toast.promise(savePromise, {
loading: "Guardando cambios...",
success: "¡Cambios guardados!",
error: "Error al guardar. Inténtalo de nuevo.",
});
}
// Toast personalizado con JSX
function showCustom() {
toast.custom((id) => (
<div className="flex items-center gap-3 rounded-lg border bg-white p-4 shadow-lg dark:bg-gray-950">
<div className="size-10 rounded-full bg-blue-100 flex items-center justify-center">
<span className="text-blue-600 text-lg">!</span>
</div>
<div className="flex-1">
<p className="text-sm font-medium">Nuevo mensaje de Alice</p>
<p className="text-xs text-muted-foreground">¡Oye, mira esta función!</p>
</div>
<Button size="sm" variant="outline" onClick={() => toast.dismiss(id)}>
Cerrar
</Button>
</div>
));
}
// Toast con deshacer
function handleDelete() {
let undone = false;
toast("Elemento eliminado", {
description: "El elemento se ha movido a la papelera.",
action: {
label: "Deshacer",
onClick: () => {
undone = true;
toast.success("¡Restaurado!");
},
},
onAutoClose: () => {
if (!undone) {
// Eliminar realmente
console.log("Permanently deleted");
}
},
});
}
return (
<div className="flex flex-wrap gap-3">
<Button onClick={showSuccess}>Éxito</Button>
<Button variant="destructive" onClick={showError}>Error + Reintentar</Button>
<Button variant="outline" onClick={handleSave}>Toast con promise</Button>
<Button variant="secondary" onClick={showCustom}>Personalizado</Button>
<Button variant="ghost" onClick={handleDelete}>Eliminar con deshacer</Button>
</div>
);
}Lo que esto demuestra:
toast.promise para operaciones async con estados de carga/éxito/errortoast.custom para toasts JSX totalmente personalizados<Toaster><Toaster> renderiza un contenedor de posición fija que gestiona el apilamiento y las animaciones de los toaststoast() es una llamada a función (no un hook) - invócala desde cualquier lugar, incluidos callbacks de server actionsConfiguración del Toaster:
<Toaster
position="top-right" // o "top-left", "bottom-left", "bottom-right", "top-center", "bottom-center"
richColors // habilita fondos de color para éxito/error
closeButton // muestra un botón de cierre en cada toast
duration={5000} // duración predeterminada de cierre automático en ms
expand={false} // si los toasts se expanden para mostrarse todos a la vez
toastOptions={{
className: "border-border",
descriptionClassName: "text-muted-foreground",
}}
/>Toast en respuesta de server action:
// Server action
"use server";
export async function updateProfile(formData: FormData) {
// ... lógica de actualización
return { success: true, message: "Perfil actualizado" };
}
// Componente cliente
async function handleSubmit(formData: FormData) {
const result = await updateProfile(formData);
if (result.success) {
toast.success(result.message);
} else {
toast.error(result.message);
}
}Toast persistente (sin cierre automático):
const toastId = toast.loading("Subiendo archivo...", {
duration: Infinity,
});
// Más tarde, actualízalo
toast.success("¡Carga completada!", { id: toastId });
// O ciérralo
toast.dismiss(toastId);Toasts temáticos con richColors:
// Con <Toaster richColors />:
toast.success("¡Guardado!"); // fondo verde
toast.error("¡Error!"); // fondo rojo
toast.info("Nota:"); // fondo azul
toast.warning("Precaución:"); // fondo amarillo// toast devuelve un ID de tipo string
const id: string | number = toast("Hola");
// Cerrar un toast específico
toast.dismiss(id);
// Cerrar todos los toasts
toast.dismiss();
// toast.promise es genérico
const promise: Promise<User> = fetchUser();
toast.promise(promise, {
loading: "Cargando usuario...",
success: (user) => `¡Bienvenido, ${user.name}!`, // user está tipado como User
error: (err) => `Error: ${err.message}`,
});
// Función de renderizado de toast personalizado
toast.custom((id: string | number) => (
<div>Contenido personalizado <button onClick={() => toast.dismiss(id)}>Cerrar</button></div>
));Falta <Toaster /> - Si olvidas añadir <Toaster /> a tu layout, las llamadas a toast() no hacen nada. Solución: Añádelo a tu layout raíz.
Toast invocado durante el renderizado - Llamar a toast() dentro del cuerpo de un componente (no en un manejador de eventos) se dispara durante el renderizado. Solución: Llama a toast() en useEffect, manejadores de eventos o después de server actions.
Demasiados toasts - Acciones rápidas pueden inundar la pantalla. Solución: Usa toast.dismiss() antes de mostrar un toast nuevo, o usa id para actualizar un toast existente.
richColors y modo oscuro - Sin richColors, los toasts de éxito/error se ven igual. Con él, obtienen fondos de color que pueden necesitar ajustes en modo oscuro. Solución: Prueba ambos temas.
Sonner vs toast de shadcn - shadcn tenía anteriormente su propio componente toast. La recomendación actual es Sonner. Solución: Usa npx shadcn@latest add sonner, no el antiguo componente toast.
| Alternativa | Úsalo cuando | No lo uses cuando |
|---|---|---|
| react-hot-toast | Quieres una alternativa ligera con API similar | Sonner cubre tus necesidades |
| Notificaciones nativas del navegador | Necesitas notificaciones a nivel de SO (con permiso) | La retroalimentación en la app es suficiente |
| Alertas inline | La retroalimentación está ligada a una sección específica de la página | Necesitas retroalimentación no bloqueante y transitoria |
| Snackbar (MUI) | Usas Material UI | Usas shadcn/ui |
<Toaster /> a tu layout raíz (app/layout.tsx)toast() no hacen nada en silencionpx shadcn@latest add sonnertoast() - notificación predeterminadatoast.success() - retroalimentación de éxitotoast.error() - retroalimentación de errortoast.info() - informativotoast.warning() - advertenciatoast.loading() - spinner de cargatoast.promise(savePromise, {
loading: "Guardando cambios...",
success: "¡Cambios guardados!",
error: "Error al guardar. Inténtalo de nuevo.",
});Transiciona automáticamente entre estados de carga, éxito y error según la promise.
onAutoClose para ejecutar la eliminación real solo si no se hizo clic en deshacertoast() solo en manejadores de eventos, useEffect o después de respuestas de server actionsconst toastId = toast.loading("Subiendo archivo...", {
duration: Infinity,
});
// Más tarde, actualízalo o ciérralo:
toast.success("¡Carga completada!", { id: toastId });toast() o toast.loading(){ id: toastId } a una llamada posterior a toast.success() o toast.error()toast.custom((id) => (
<div className="flex items-center gap-3 rounded-lg border p-4 shadow-lg">
<p className="text-sm font-medium">Mensaje personalizado</p>
<Button size="sm" onClick={() => toast.dismiss(id)}>Cerrar</Button>
</div>
));npx shadcn@latest add sonner)toast en proyectos nuevosconst promise: Promise<User> = fetchUser();
toast.promise(promise, {
loading: "Cargando usuario...",
success: (user) => `¡Bienvenido, ${user.name}!`, // user está tipado como User
error: (err) => `Error: ${err.message}`,
});top-right, top-left, top-centerbottom-right, bottom-left, bottom-center<Toaster position="top-right" />Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥