Alerta
Un componente de banner inline que comunica mensajes de estado, advertencias o información contextual al usuario dentro del flujo normal de la página.
Busca en todas las páginas de la documentación
Un componente de banner inline que comunica mensajes de estado, advertencias o información contextual al usuario dentro del flujo normal de la página.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
interface AlertProps {
children: React.ReactNode;
}
export function Alert({ children }: AlertProps) {
return (
<div role="alert" className="rounded-lg border border-blue-200 bg-blue-50 p-4 text-sm text-blue-800">
{children}
</div>
);
}Una alerta informativa estática con role="alert" para que los lectores de pantalla la anuncien inmediatamente cuando aparezca en el DOM. No se necesita "use client" ya que no hay interactividad.
interface AlertProps {
title?: string;
children: React.ReactNode;
}
export function AlertInfo({ title, children }: AlertProps) {
return (
<div role="alert" className="rounded-lg border border-blue-200 bg-blue-50 p-4">
{title && <p className="mb-1 text-sm font-semibold text-blue-900">{title}</p>}
<p className="text-sm text-blue-800">{children}</p>
</div>
);
}La variante de información usa tonos azules para indicar contenido neutral e informativo. La prop opcional title agrega una línea de encabezado en negrita arriba de la descripción para mensajería de dos niveles.
interface AlertProps {
title?: string;
children: React.ReactNode;
}
export function AlertSuccess({ title, children }: AlertProps) {
return (
<div role="alert" className="rounded-lg border border-green-200 bg-green-50 p-4">
{title && <p className="mb-1 text-sm font-semibold text-green-900">{title}</p>}
<p className="text-sm text-green-800">{children}</p>
</div>
);
}El verde señala un resultado positivo -- formulario guardado, pago procesado, cuenta verificada. Esta variante generalmente se muestra después de una acción exitosa y puede emparejarse con un botón de cierre descartable.
interface AlertProps {
title?: string;
children: React.ReactNode;
}
export function AlertWarning({ title, children }: AlertProps) {
return (
<div role="alert" className="rounded-lg border border-yellow-200 bg-yellow-50 p-4">
{title && <p className="mb-1 text-sm font-semibold text-yellow-900">{title}</p>}
<p className="text-sm text-yellow-800">{children}</p>
</div>
);
}El amarillo llama la atención sin implicar fracaso. Usa esto para condiciones que necesitan reconocimiento pero no son bloqueantes -- espacio en disco bajo, límites de velocidad que se aproximan, o características obsoletas aún en uso.
interface AlertProps {
title?: string;
children: React.ReactNode;
}
export function AlertError({ title, children }: AlertProps) {
return (
<div role="alert" className="rounded-lg border border-red-200 bg-red-50 p-4">
{title && <p className="mb-1 text-sm font-semibold text-red-900">{title}</p>}
<p className="text-sm text-red-800">{children}</p>
</div>
);
}El rojo comunica problemas críticos que requieren atención inmediata -- errores de validación, solicitudes fallidas, o confirmaciones de acciones destructivas. Considera usar aria-live="assertive" para errores que aparecen dinámicamente.
type AlertVariant = "info" | "success" | "warning" | "error";
interface AlertProps {
variant?: AlertVariant;
title?: string;
children: React.ReactNode;
}
const variantStyles: Record<AlertVariant, { container: string; icon: string }> = {
info: {
container: "border-blue-200 bg-blue-50 text-blue-800",
icon: "text-blue-500",
},
success: {
container: "border-green-200 bg-green-50 text-green-800",
icon: "text-green-500",
},
warning: {
container: "border-yellow-200 bg-yellow-50 text-yellow-800",
icon: "text-yellow-500",
},
error: {
container: "border-red-200 bg-red-50 text-red-800",
icon: "text-red-500",
},
};
const icons: Record<AlertVariant, React.ReactNode> = {
info: (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
),
success: (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
warning: (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
),
error: (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
};
export function Alert({ variant = "info", title, children }: AlertProps) {
const styles = variantStyles[variant];
return (
<div role="alert" className={`flex gap-3 rounded-lg border p-4 ${styles.container}`}>
<span className={`shrink-0 ${styles.icon}`}>{icons[variant]}</span>
<div>
{title && <p className="mb-1 text-sm font-semibold">{title}</p>}
<p className="text-sm">{children}</p>
</div>
</div>
);
}Los iconos refuerzan el significado de la alerta más allá del color solo, lo cual es esencial para la accesibilidad. El shrink-0 en el contenedor del icono evita que se comprima cuando el contenido de texto es largo. El diseño flex con gap-3 mantiene espaciado consistente entre el icono y el texto.
"use client";
import { useState } from "react";
type AlertVariant = "info" | "success" | "warning" | "error";
interface AlertProps {
variant?: AlertVariant;
title?: string;
children: React.ReactNode;
onDismiss?: () => void;
}
const variantStyles: Record<AlertVariant, string> = {
info: "border-blue-200 bg-blue-50 text-blue-800",
success: "border-green-200 bg-green-50 text-green-800",
warning: "border-yellow-200 bg-yellow-50 text-yellow-800",
error: "border-red-200 bg-red-50 text-red-800",
};
export function DismissibleAlert({ variant = "info", title, children, onDismiss }: AlertProps) {
const [visible, setVisible] = useState(true);
if (!visible) return null;
function handleDismiss() {
setVisible(false);
onDismiss?.();
}
return (
<div role="alert" className={`flex items-start gap-3 rounded-lg border p-4 ${variantStyles[variant]}`}>
<div className="flex-1">
{title && <p className="mb-1 text-sm font-semibold">{title}</p>}
<p className="text-sm">{children}</p>
</div>
<button
type="button"
onClick={handleDismiss}
aria-label="Dismiss alert"
className="shrink-0 rounded-md p-1 opacity-60 hover:opacity-100"
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
);
}Gestiona su propia visibilidad con estado interno mientras también expone un callback onDismiss para que el componente padre reaccione (p. ej., persistir el descarte). El botón de cierre usa aria-label ya que no tiene texto visible. Requiere "use client" para state.
type AlertVariant = "info" | "success" | "warning" | "error";
interface AlertProps {
variant?: AlertVariant;
children: React.ReactNode;
actionLabel: string;
actionHref: string;
}
const variantStyles: Record<AlertVariant, { container: string; link: string }> = {
info: { container: "border-blue-200 bg-blue-50 text-blue-800", link: "text-blue-700 hover:text-blue-900" },
success: { container: "border-green-200 bg-green-50 text-green-800", link: "text-green-700 hover:text-green-900" },
warning: { container: "border-yellow-200 bg-yellow-50 text-yellow-800", link: "text-yellow-700 hover:text-yellow-900" },
error: { container: "border-red-200 bg-red-50 text-red-800", link: "text-red-700 hover:text-red-900" },
};
export function AlertWithAction({ variant = "info", children, actionLabel, actionHref }: AlertProps) {
const styles = variantStyles[variant];
return (
<div role="alert" className={`flex items-center justify-between gap-4 rounded-lg border p-4 ${styles.container}`}>
<p className="text-sm">{children}</p>
<a
href={actionHref}
className={`shrink-0 text-sm font-semibold underline ${styles.link}`}
>
{actionLabel}
</a>
</div>
);
}
// Uso
<AlertWithAction variant="warning" actionLabel="Actualizar plan" actionHref="/billing">
Has utilizado el 90% de tu cuota mensual de API.
</AlertWithAction>El enlace de acción se sitúa en el borde derecho de la alerta usando justify-between, dándole prominencia visual sin romper el flujo del mensaje. El shrink-0 en el enlace evita que se ajuste cuando el texto del mensaje es largo.
"use client";
import { forwardRef, useState, useEffect, useCallback, type ReactNode } from "react";
type AlertVariant = "info" | "success" | "warning" | "error";
interface AlertAction {
label: string;
onClick: () => void;
}
interface AlertProps {
variant?: AlertVariant;
title?: string;
children: ReactNode;
icon?: ReactNode;
dismissible?: boolean;
onDismiss?: () => void;
autoClose?: number;
action?: AlertAction;
className?: string;
}
const variantConfig: Record<
AlertVariant,
{ container: string; icon: ReactNode; iconColor: string }
> = {
info: {
container: "border-blue-200 bg-blue-50 text-blue-800",
iconColor: "text-blue-500",
icon: (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
),
},
success: {
container: "border-green-200 bg-green-50 text-green-800",
iconColor: "text-green-500",
icon: (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
},
warning: {
container: "border-yellow-200 bg-yellow-50 text-yellow-800",
iconColor: "text-yellow-500",
icon: (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
),
},
error: {
container: "border-red-200 bg-red-50 text-red-800",
iconColor: "text-red-500",
icon: (
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
},
};
export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
{
variant = "info",
title,
children,
icon,
dismissible = false,
onDismiss,
autoClose,
action,
className,
},
ref
) {
const [visible, setVisible] = useState(true);
const [exiting, setExiting] = useState(false);
const config = variantConfig[variant];
const dismiss = useCallback(() => {
setExiting(true);
const timer = setTimeout(() => {
setVisible(false);
onDismiss?.();
}, 200);
return () => clearTimeout(timer);
}, [onDismiss]);
useEffect(() => {
if (!autoClose) return;
const timer = setTimeout(dismiss, autoClose);
return () => clearTimeout(timer);
}, [autoClose, dismiss]);
if (!visible) return null;
const displayIcon = icon ?? config.icon;
return (
<div
ref={ref}
role="alert"
aria-live={variant === "error" ? "assertive" : "polite"}
className={[
"flex items-start gap-3 rounded-lg border p-4 transition-opacity duration-200",
config.container,
exiting ? "opacity-0" : "opacity-100",
className ?? "",
].join(" ")}
>
<span className={`shrink-0 pt-px ${config.iconColor}`}>{displayIcon}</span>
<div className="flex-1">
{title && (
<p className="mb-1 text-sm font-semibold leading-5">{title}</p>
)}
<div className="text-sm leading-5">{children}</div>
{action && (
<button
type="button"
onClick={action.onClick}
className="mt-2 text-sm font-semibold underline underline-offset-2 hover:no-underline"
>
{action.label}
</button>
)}
</div>
{dismissible && (
<button
type="button"
onClick={dismiss}
aria-label="Dismiss alert"
className="shrink-0 rounded-md p-1 opacity-60 transition-opacity hover:opacity-100"
>
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
)}
</div>
);
});
// Uso
function SettingsPage() {
const [saved, setSaved] = useState(false);
return (
<div className="space-y-4">
{saved && (
<Alert
variant="success"
title="Configuración guardada"
dismissible
autoClose={5000}
onDismiss={() => setSaved(false)}
>
Tus preferencias han sido actualizadas exitosamente.
</Alert>
)}
<Alert
variant="warning"
title="Compatibilidad del navegador"
action={{ label: "Saber más", onClick: () => console.log("clicked") }}
>
Algunas características podrían no funcionar en Internet Explorer 11.
</Alert>
<Alert variant="error" title="Conexión fallida" dismissible>
No se puede alcanzar el servidor. Verifica tu red e intenta de nuevo.
</Alert>
</div>
);
}Aspectos clave:
autoClose acepta una duración en milisegundos y descarta automáticamente la alerta. El timeout se limpia al desmontar para evitar actualizaciones de estado en componentes removidos.exiting antes de remover el elemento, dando una sensación pulida sin requerir una librería de animación.aria-live="assertive" para interrumpir el lector de pantalla inmediatamente, mientras que otras variantes usan "polite" para esperar una pausa natural en el habla.icon permite a los consumidores sustituir su propio SVG o componente de icono sin cambiar la estructura del diseño.action opcional coloca un botón inline debajo del mensaje, manteniendo la alerta autocontenida. Esto evita la necesidad de que los consumidores compongan contenido personalizado para patrones comunes de llamada a la acción.role="alert" anuncia en cada re-renderizado -- si el texto de la alerta cambia debido a un re-renderizado del componente padre, el lector de pantalla re-anuncia todo el contenido. Evita colocar alertas dentro de componentes que se re-renderizar frecuentemente, o memoiza la alerta.
El color solo comunicando severidad -- rojo para error y verde para éxito no tiene sentido para usuarios daltónicos. Siempre empareja el color con un icono y texto descriptivo para comunicar el tipo de alerta.
El cierre automático en alertas de error frustra a los usuarios -- los mensajes de error que desaparecen antes de que el usuario tenga tiempo de leerlos y actuar sobre ellos causa confusión. Evita autoClose en variantes de error y advertencia.
El descarte remueve el elemento del DOM -- después del descarte, la alerta se ha ido y no puede ser restaurada sin que el componente padre la remonte. Si necesitas "deshacer descarte", mantén la alerta en el DOM pero visualmente oculta, o gestiona el estado en el componente padre.
Múltiples alertas apiladas sin espaciado -- renderizar varias alertas seguidas sin una utilidad flex/gap o margin crea un diseño visualmente abarrotado. Envuelve las alertas en un contenedor space-y-3.
La región aria-live no se detecta en el renderizado inicial -- los lectores de pantalla solo anuncian cambios a una región aria-live, no su contenido inicial. Si la alerta está presente en la primera pintura, el usuario no la escuchará a menos que navegue a ella manualmente.
Revisado por Chris St. John·Última actualización: 10 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥