Alerta
Um componente de banner inline que comunica mensagens de status, avisos ou informações contextuais ao usuário dentro do fluxo normal da página.
Busque em todas as páginas da documentação
Um componente de banner inline que comunica mensagens de status, avisos ou informações contextuais ao usuário dentro do fluxo normal da 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>
);
}Um alerta informativo estático com role="alert" para que leitores de tela o anunciem imediatamente quando ele aparecer no DOM. Não é necessário "use client", pois não há interatividade.
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>
);
}A variante de informação usa tons de azul para indicar conteúdo neutro e informativo. O prop opcional title adiciona uma linha de título em negrito acima da descrição para mensagens de dois níveis.
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>
);
}O verde sinaliza um resultado positivo - formulário salvo, pagamento processado, conta verificada. Esta variante é normalmente exibida após uma ação bem-sucedida e pode ser combinada com um botão de fechar que pode ser dispensado.
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>
);
}O amarelo chama a atenção sem implicar falha. Use-o para condições que precisam de reconhecimento, mas não são bloqueadoras - pouco espaço em disco, limites de taxa se aproximando ou recursos obsoletos ainda em 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>
);
}O vermelho comunica problemas críticos que exigem atenção imediata - erros de validação, requisições falhas ou confirmações de ações destrutivas. Considere usar aria-live="assertive" para erros que aparecem dinamicamente.
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>
);
}Ícones reforçam o significado do alerta além da cor, o que é essencial para acessibilidade. O shrink-0 no wrapper do ícone evita que ele seja comprimido quando o conteúdo do texto é longo. O layout flexível com gap-3 mantém um espaçamento consistente entre o ícone e o 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>
);
}Gerencia sua própria visibilidade com estado interno, ao mesmo tempo que expõe um callback onDismiss para o pai reagir (por exemplo, persistir a dispensa). O botão de fechar usa aria-label, pois não tem texto visível. Requer "use client" para estado.
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>
);
}
// Usage
<AlertWithAction variant="warning" actionLabel="Upgrade plan" actionHref="/billing">
You have used 90% of your monthly API quota.
</AlertWithAction>O link de ação fica na borda direita do alerta usando justify-between, dando-lhe destaque visual sem quebrar o fluxo da mensagem. O shrink-0 no link evita que ele quebre quando o texto da mensagem for longo.
"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>
);
});
// Usage
function SettingsPage() {
const [saved, setSaved] = useState(false);
return (
<div className="space-y-4">
{saved && (
<Alert
variant="success"
title="Settings saved"
dismissible
autoClose={5000}
onDismiss={() => setSaved(false)}
>
Your preferences have been updated successfully.
</Alert>
)}
<Alert
variant="warning"
title="Browser support"
action={{ label: "Learn more", onClick: () => console.log("clicked") }}
>
Some features may not work in Internet Explorer 11.
</Alert>
<Alert variant="error" title="Connection failed" dismissible>
Unable to reach the server. Check your network and try again.
</Alert>
</div>
);
}Aspectos Chave:
autoClose aceita uma duração em milissegundos e fecha o alerta automaticamente. O timeout é limpo ao desmontar para evitar atualizações de estado em componentes removidos.exiting antes de remover o elemento, proporcionando um acabamento polido sem a necessidade de uma biblioteca de animação.aria-live adaptativo -- alertas de erro usam aria-live="assertive" para interromper o leitor de tela imediatamente, enquanto outras variantes usam "polite" para esperar uma pausa natural na fala.icon permite que os consumidores substituam seu próprio SVG ou componente de ícone sem alterar a estrutura do layout.action coloca um botão inline abaixo da mensagem, mantendo o alerta autocontido. Isso evita a necessidade de os consumidores comporem conteúdo personalizado para padrões comuns de chamada para ação.forwardRef -- permite que componentes pais meçam a altura do alerta para animações suaves de entrada/saída ou para rolar o alerta para a visualização após a inserção dinâmica.role="alert" anuncia a cada re-renderização -- se o texto do alerta mudar devido a uma re-renderização do pai, o leitor de tela re-anuncia todo o conteúdo. Evite colocar alertas dentro de componentes que re-renderizam frequentemente, ou memoize o alerta.
A cor sozinha transmitindo severidade -- vermelho para erro e verde para sucesso é sem significado para usuários daltônicos. Sempre combine a cor com um ícone e texto descritivo para comunicar o tipo de alerta.
Fechamento automático em alertas de erro frustra os usuários -- mensagens de erro que desaparecem antes que o usuário tenha tempo de ler e agir sobre elas causam confusão. Evite autoClose em variantes de erro e aviso.
Dispensar remove o elemento do DOM -- após dispensar, o alerta desaparece e não pode ser restaurado sem que o pai o remonte. Se você precisar de "desfazer dispensa", mantenha o alerta no DOM, mas visualmente oculto, ou gerencie o estado no pai.
Múltiplos alertas empilhados sem espaçamento -- renderizar vários alertas em sequência sem um utilitário de flex/gap ou margem cria um layout visualmente apertado. Envolva os alertas em um contêiner space-y-3.
Região aria-live não detectada na renderização inicial -- leitores de tela anunciam apenas mudanças em uma região aria-live, não seu conteúdo inicial. Se o alerta estiver presente na primeira renderização, o usuário não o ouvirá, a menos que navegue até ele manualmente.
Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥