Alert
Displays a clean contextual banner for user attention with theme-adaptive neutral backgrounds, colored titles & icons, and custom slot support.
Core implementation of the Alert component featuring clean neutral cards, status-colored titles, and custom slots.
"use client";
import * as React from "react";
import { AlertCircle, CheckCircle2, Info, AlertTriangle, X } from "lucide-react";
import { cn } from "@/lib/utils";
type AlertColor =
| "default"
| "primary"
| "secondary"
| "accent"
| "info"
| "success"
| "warning"
| "danger";
type AlertVariant = "default" | "bordered" | "flat" | "ghost" | "shadow";
interface AlertContextValue {
color: AlertColor;
variant: AlertVariant;
}
const AlertContext = React.createContext<AlertContextValue>({
color: "info",
variant: "default",
});
const useAlertContext = () => React.useContext(AlertContext);
const titleColorMap: Record<AlertColor, string> = {
default: "text-zinc-900 dark:text-zinc-100 font-semibold",
primary: "text-sky-600 dark:text-sky-400 font-semibold",
secondary: "text-purple-600 dark:text-purple-400 font-semibold",
accent: "text-pink-600 dark:text-pink-400 font-semibold",
info: "text-sky-600 dark:text-sky-400 font-semibold",
success: "text-emerald-600 dark:text-emerald-400 font-semibold",
warning: "text-amber-600 dark:text-amber-400 font-semibold",
danger: "text-rose-600 dark:text-rose-400 font-semibold",
};
const iconColorMap: Record<AlertColor, string> = {
default: "text-zinc-500 dark:text-zinc-400",
primary: "text-sky-500",
secondary: "text-purple-500",
accent: "text-pink-500",
info: "text-sky-500",
success: "text-emerald-500",
warning: "text-amber-500",
danger: "text-rose-500",
};
const variantCardMap: Record<AlertVariant, string> = {
default: "bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 border border-zinc-200/80 dark:border-zinc-800/80 shadow-xs",
bordered: "bg-transparent text-zinc-900 dark:text-zinc-100 border border-zinc-200 dark:border-zinc-800",
flat: "bg-zinc-100/90 dark:bg-zinc-900/60 text-zinc-900 dark:text-zinc-100 border border-transparent",
ghost: "bg-transparent text-zinc-900 dark:text-zinc-100 border border-transparent",
shadow: "bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 border border-zinc-200/60 dark:border-zinc-800/60 shadow-md",
};
const iconMap: Record<AlertColor, React.ElementType> = {
default: Info,
primary: Info,
secondary: Info,
accent: Info,
info: Info,
success: CheckCircle2,
warning: AlertTriangle,
danger: AlertCircle,
};
export interface AlertProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
variant?: AlertVariant;
color?: AlertColor;
title?: React.ReactNode;
icon?: React.ReactNode;
startContent?: React.ReactNode;
endContent?: React.ReactNode;
hideIcon?: boolean;
isClosable?: boolean;
onClose?: () => void;
action?: React.ReactNode;
}
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
(
{
className,
variant = "default",
color = "info",
title,
icon,
startContent,
endContent,
hideIcon = false,
isClosable = false,
onClose,
action,
children,
...props
},
ref
) => {
const [isVisible, setIsVisible] = React.useState(true);
if (!isVisible) return null;
const handleClose = () => {
setIsVisible(false);
onClose?.();
};
const IconComponent = iconMap[color];
const renderedStart = startContent ?? (
!hideIcon && (
icon ?? (
<IconComponent
className={cn("size-5 shrink-0 mt-0.5", iconColorMap[color])}
/>
)
)
);
return (
<AlertContext.Provider value={{ color, variant }}>
<div
ref={ref}
role="alert"
className={cn(
"relative w-full rounded-2xl p-4 text-sm flex gap-3.5 items-start leading-relaxed transition-all duration-200",
variantCardMap[variant],
className
)}
{...props}
>
{renderedStart}
<div className="flex flex-col flex-1 min-w-0">
{title && <AlertTitle>{title}</AlertTitle>}
{children && (
typeof children === "string" ? (
<AlertDescription>{children}</AlertDescription>
) : (
children
)
)}
{action && <div className="mt-3">{action}</div>}
</div>
{(endContent || isClosable) && (
<div className="flex items-center gap-2 shrink-0 self-start">
{endContent}
{isClosable && (
<button
type="button"
onClick={handleClose}
className="p-1 rounded-lg text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 hover:bg-zinc-200/50 dark:hover:bg-zinc-800/60 transition-colors cursor-pointer"
aria-label="Close alert"
>
<X className="size-4" />
</button>
)}
</div>
)}
</div>
</AlertContext.Provider>
);
}
);
Alert.displayName = "Alert";
const AlertTitle = React.forwardRef<
HTMLHeadingElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, children, ...props }, ref) => {
const { color } = useAlertContext();
return (
<h5
ref={ref}
className={cn(
"font-semibold text-sm leading-none tracking-tight",
titleColorMap[color],
className
)}
{...props}
>
{children}
</h5>
);
});
AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, children, ...props }, ref) => (
<div
ref={ref}
className={cn(
"text-xs text-zinc-600 dark:text-zinc-400 leading-relaxed mt-1.5",
className
)}
{...props}
>
{children}
</div>
));
AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription };
export type { AlertColor, AlertVariant };
Default
A clean, subtle alert banner with a neutral background, status-colored title, and status icon.
infotitle: stringSystem Information
Colors
Select semantic colors via the color prop. The card background stays clean and neutral while only the title and status icon inherit the semantic accent color.
default | primary | secondary | accent | info | success | warning | dangerInformation
Payment Successful
Storage Limit Warning
Connection Error
Primary Highlight
Variants
Control the card framing and depth using the variant prop (default, bordered, flat, ghost, shadow).
default | bordered | flat | ghost | shadowDefault Variant
Bordered Variant
Flat Variant
Shadow Variant
Start & End Slots (startContent & endContent)
Inject custom elements before the title using startContent, or trailing action controls and badges using endContent.
New Team Member
Deployment Completed
Dismissible Alert (isClosable)
Enable dismissible behavior by setting isClosable to true. Handles state internally and triggers onClose callback when dismissed.
Maintenance Scheduled
Compound Components (AlertTitle & AlertDescription)
Compose flexible alert layouts using AlertTitle and AlertDescription subcomponents.
Security Vulnerability Detected
Props — Alert
Properties for configuring the Alert banner component.
| Prop | Type | Default | Description |
|---|---|---|---|
| color | 'default' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'danger' | 'info' | Semantic color applied exclusively to the title header and status icon. |
| variant | 'default' | 'bordered' | 'flat' | 'ghost' | 'shadow' | 'default' | Visual card style determining background depth and border framing. |
| title | ReactNode | — | Title header text rendered with the status color. |
| startContent | ReactNode | — | Custom element (icon, avatar) rendered before the title and message. |
| endContent | ReactNode | — | Custom element (action button, badge) rendered on the right side. |
| icon | ReactNode | — | Custom icon replacing the default status type icon. |
| hideIcon | boolean | false | Hides the leading icon completely. |
| isClosable | boolean | false | Renders a close button on the right to dismiss the alert banner. |
| onClose | () => void | — | Callback function fired when the alert is dismissed. |
Props — AlertTitle
Properties for configuring the AlertTitle component.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Custom CSS class names for styling the title element. |
Props — AlertDescription
Properties for configuring the AlertDescription component.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Custom CSS class names for styling the description container. |