Use Alert for a message that belongs in the page: a failed save, permission notice, or service warning. It does not open a modal or dismiss itself. Use Sonner for transient feedback.
Live preview
import { CircleCheckBig } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
export default function AlertDemo() {
return (
<Alert className="w-full max-w-xl">
<CircleCheckBig aria-hidden="true" />
<AlertTitle>Registry checks passed</AlertTitle>
<AlertDescription>
The schema, generated files, and fresh consumer builds are ready for review.
</AlertDescription>
</Alert>
);
}
#Installation
Complete the base setup, then use the CLI or create the source file manually:
First time? Install the shared base once.
npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/alert.jsonCreate components/ui/alert.tsx:
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-base border-2 border-border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_minmax(0,1fr)] grid-cols-[0_minmax(0,1fr)] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current shadow-shadow",
{
variants: {
variant: {
default: "bg-main text-main-foreground",
destructive: "bg-black text-white",
},
},
defaultVariants: {
variant: "default",
},
},
);
function Alert({
className,
variant,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
);
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-title"
className={cn(
"col-start-2 min-h-4 min-w-0 wrap-anywhere font-heading tracking-tight",
className,
)}
{...props}
/>
);
}
function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-description"
className={cn(
"col-start-2 grid min-w-0 justify-items-start gap-1 wrap-anywhere text-sm font-base [&_p]:leading-relaxed",
className,
)}
{...props}
/>
);
}
export { Alert, AlertTitle, AlertDescription };
#Usage
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
export default function SaveError() {
return (
<Alert variant="destructive">
<AlertTitle>Changes were not saved</AlertTitle>
<AlertDescription>Check your connection, then try saving again.</AlertDescription>
</Alert>
);
}#API reference
| Prop / part | Purpose | Default |
|---|---|---|
variant | default or destructive | default |
AlertTitle | Short summary | — |
AlertDescription | Explanation or recovery steps | — |
children | Icon, text, and optional actions | — |
role | Announcement semantics | alert |
className | Layout and width overrides | — |
#Accessibility
The default alert role announces important updates. Do not use it for every decorative card. For non-urgent updates, choose role="status" or ordinary page text as appropriate. Use words, not color alone, to explain severity. Hide decorative icons with aria-hidden.
#Examples
#Destructive
import { AlertCircleIcon } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
export default function AlertDestructiveDemo() {
return (
<Alert variant="destructive">
<AlertCircleIcon />
<AlertTitle>Something went wrong!</AlertTitle>
<AlertDescription>Your session has expired. Please log in again.</AlertDescription>
</Alert>
);
}
#Icon and description
import { BookmarkCheckIcon } from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert";
export default function AlertIconDescriptionDemo() {
return (
<Alert>
<BookmarkCheckIcon />
<AlertDescription>This one has an icon and a description only. No title.</AlertDescription>
</Alert>
);
}
#Description only
import { Alert, AlertDescription } from "@/components/ui/alert";
export default function AlertDescriptionOnlyDemo() {
return (
<Alert>
<AlertDescription>This one has a description only. No title. No icon.</AlertDescription>
</Alert>
);
}
#Icon and title
import { PopcornIcon } from "lucide-react";
import { Alert, AlertTitle } from "@/components/ui/alert";
export default function AlertIconTitleDemo() {
return (
<Alert>
<PopcornIcon />
<AlertTitle>Let's try one with icon and title.</AlertTitle>
</Alert>
);
}
#Long title
import { ShieldAlertIcon } from "lucide-react";
import { Alert, AlertTitle } from "@/components/ui/alert";
export default function AlertLongTitleDemo() {
return (
<Alert>
<ShieldAlertIcon />
<AlertTitle>
This is a very long alert title that demonstrates how the component handles extended text
content and potentially wraps across multiple lines
</AlertTitle>
</Alert>
);
}
#Long description
import { GiftIcon } from "lucide-react";
import { Alert, AlertDescription } from "@/components/ui/alert";
export default function AlertLongDescriptionDemo() {
return (
<Alert>
<GiftIcon />
<AlertDescription>
This is a very long alert description that demonstrates how the component handles extended
text content and potentially wraps across multiple lines
</AlertDescription>
</Alert>
);
}
#Long title and description
import { AlertCircleIcon } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
export default function AlertLongTitleAndDescriptionDemo() {
return (
<Alert>
<AlertCircleIcon />
<AlertTitle>
This is an extremely long alert title that spans multiple lines to demonstrate how the
component handles very lengthy headings while maintaining readability and proper text
wrapping behavior
</AlertTitle>
<AlertDescription>
This is an equally long description that contains detailed information about the alert. It
shows how the component can accommodate extensive content while preserving proper spacing,
alignment, and readability across different screen sizes and viewport widths. This helps
ensure the user experience remains consistent regardless of the content length.
</AlertDescription>
</Alert>
);
}
#Additional examples
#With button
"use client";
import { CheckCircle2Icon } from "lucide-react";
import { useState } from "react";
import { Alert, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
export default function AlertWithButtonDemo() {
const [undone, setUndone] = useState(false);
return (
<Alert>
<CheckCircle2Icon aria-hidden="true" />
<AlertTitle className="flex items-start justify-between gap-3">
<span className="min-w-0">
{undone
? "The selected emails have been restored to the inbox."
: "The selected emails have been marked as spam."}
</span>
<Button
size="sm"
variant="noShadow"
className="shrink-0 bg-secondary-background text-foreground"
disabled={undone}
onClick={() => setUndone(true)}
>
{undone ? "Undone" : "Undo"}
</Button>
</AlertTitle>
</Alert>
);
}