An inline callout for messages that belong to the page — status, warnings, errors and announcements. Four styles, seven colors, default or custom icons, trailing actions and an optional dismiss button.
Refresh the page to get the latest features and fixes.
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";export default function AlertDemo() { return ( <Alert showIcon className="max-w-md"> <AlertTitle>A new version is available</AlertTitle> <AlertDescription> Refresh the page to get the latest features and fixes. </AlertDescription> </Alert> );}
The default role follows the color. danger and warning alerts render
with role="alert" (assertive — announced immediately, interrupting the
user); every other color renders with role="status" (polite — announced
when the user is idle). For static notices that are on the page from the
start, pass role="note" so they aren't announced at all.
soft (default) tints the whole surface. outline is a plain card with a colored icon, accent adds a colored leading bar for dense layouts, and solid fills with the tone for banners that must not be missed.
Soft
The default. A tinted surface for most in-page messages.
Outline
A card surface where only the icon carries the color.
Accent
A colored bar on the leading edge, for dense or neutral layouts.
Solid
Full color, for banners and messages that must stand out.
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";export default function AlertVariants() { return ( <div className="flex w-full max-w-md flex-col gap-3"> <Alert variant="soft" color="info" showIcon> <AlertTitle>Soft</AlertTitle> <AlertDescription> The default. A tinted surface for most in-page messages. </AlertDescription> </Alert> <Alert variant="outline" color="info" showIcon> <AlertTitle>Outline</AlertTitle> <AlertDescription> A card surface where only the icon carries the color. </AlertDescription> </Alert> <Alert variant="accent" color="info" showIcon> <AlertTitle>Accent</AlertTitle> <AlertDescription> A colored bar on the leading edge, for dense or neutral layouts. </AlertDescription> </Alert> <Alert variant="solid" color="info" showIcon> <AlertTitle>Solid</AlertTitle> <AlertDescription> Full color, for banners and messages that must stand out. </AlertDescription> </Alert> </div> );}
Match the color to the message: info (default) for neutral information, success for completed actions, warning for something that needs attention soon, danger for errors and failures, and neutral for low-emphasis notes. primary and brand are also available for product announcements.
Scheduled maintenance
The dashboard will be read-only on Sunday, 02:00–04:00 UTC.
Payment received
Invoice INV-2041 for $1,280.00 has been paid.
Storage almost full
You've used 92% of your 50 GB quota.
Deployment failed
The build for main@4f2c1ab exited with code 1.
Draft
This page isn't published yet. Only editors can see it.
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";const items = [ { color: "info", title: "Scheduled maintenance", text: "The dashboard will be read-only on Sunday, 02:00–04:00 UTC.", }, { color: "success", title: "Payment received", text: "Invoice INV-2041 for $1,280.00 has been paid.", }, { color: "warning", title: "Storage almost full", text: "You've used 92% of your 50 GB quota.", }, { color: "danger", title: "Deployment failed", text: "The build for main@4f2c1ab exited with code 1.", }, { color: "neutral", title: "Draft", text: "This page isn't published yet. Only editors can see it.", },] as const;export default function AlertColors() { return ( <div className="flex w-full max-w-md flex-col gap-3"> {items.map((item) => ( <Alert key={item.color} color={item.color} showIcon> <AlertTitle>{item.title}</AlertTitle> <AlertDescription>{item.text}</AlertDescription> </Alert> ))} </div> );}
Both parts are optional. Use a title alone for one-line confirmations, a description alone for explanatory notes, and both when the summary needs context.
Your profile has been updated.
Invoices are generated on the 1st of each month and emailed to the billing contact.
Two-factor authentication is off
Anyone with your password can sign in to this account.
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";export default function AlertContent() { return ( <div className="flex w-full max-w-md flex-col gap-3"> <Alert color="success" showIcon> <AlertTitle>Your profile has been updated.</AlertTitle> </Alert> <Alert color="info" showIcon> <AlertDescription> Invoices are generated on the 1st of each month and emailed to the billing contact. </AlertDescription> </Alert> <Alert color="warning" showIcon> <AlertTitle>Two-factor authentication is off</AlertTitle> <AlertDescription> Anyone with your password can sign in to this account. </AlertDescription> </Alert> </div> );}
showIcon adds the tone's default icon (info, check, triangle or circle; neutral has none). Pass icon to use any other icon; it gets the same size and tint.
Workflows are here
Automate reviews, deploys and notifications without leaving the app.
You're offline
Changes are saved locally and will sync when you reconnect.
API key expires in 7 days
Rotate the key before June 14 to avoid failed requests.
import { KeyRoundIcon, RocketIcon, WifiOffIcon } from "lucide-react";import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";export default function AlertCustomIcon() { return ( <div className="flex w-full max-w-md flex-col gap-3"> <Alert color="brand" icon={<RocketIcon />}> <AlertTitle>Workflows are here</AlertTitle> <AlertDescription> Automate reviews, deploys and notifications without leaving the app. </AlertDescription> </Alert> <Alert color="neutral" variant="outline" icon={<WifiOffIcon />}> <AlertTitle>You're offline</AlertTitle> <AlertDescription> Changes are saved locally and will sync when you reconnect. </AlertDescription> </Alert> <Alert color="warning" variant="accent" icon={<KeyRoundIcon />}> <AlertTitle>API key expires in 7 days</AlertTitle> <AlertDescription> Rotate the key before June 14 to avoid failed requests. </AlertDescription> </Alert> </div> );}
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";import { Link } from "@/components/ui/link";export default function AlertRichContent() { return ( <Alert color="danger" showIcon className="max-w-md"> <AlertTitle>We couldn't import 3 rows</AlertTitle> <AlertDescription> <ul className="list-disc space-y-0.5 pl-4"> <li>Row 14: “email” is not a valid address.</li> <li>Row 27: “plan” must be Free, Pro or Team.</li> <li>Row 31: “seats” must be a whole number.</li> </ul> <p className="mt-2"> Fix the file and upload it again, or{" "} <Link href="#" className="text-sm"> download an error report </Link> . </p> </AlertDescription> </Alert> );}
action renders buttons on the trailing side, vertically centered. Keep it to one or two small buttons, and make sure the action is reachable elsewhere if the alert can be dismissed.
Your trial ends in 3 days
Upgrade to keep your projects and history.
Update ready
Version 2.4.0 has been downloaded.
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";import { Button } from "@/components/ui/button";export default function AlertActions() { return ( <div className="flex w-full max-w-lg flex-col gap-3"> <Alert color="warning" variant="accent" showIcon action={ <Button size="sm" variant="outline"> Upgrade </Button> } > <AlertTitle>Your trial ends in 3 days</AlertTitle> <AlertDescription> Upgrade to keep your projects and history. </AlertDescription> </Alert> <Alert color="info" showIcon action={ <> <Button size="sm" variant="ghost"> Later </Button> <Button size="sm">Restart</Button> </> } > <AlertTitle>Update ready</AlertTitle> <AlertDescription>Version 2.4.0 has been downloaded.</AlertDescription> </Alert> </div> );}
onDismiss adds a close button. The alert doesn't hide itself — remove it from your state in the callback, and persist the choice if it shouldn't come back on reload.
Domain verified
acme.com is connected. SSL certificates are issued automatically.
import { useState } from "react";import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";import { Button } from "@/components/ui/button";export default function AlertDismissible() { const [open, setOpen] = useState(true); if (!open) { return ( <Button variant="outline" size="sm" onPress={() => setOpen(true)}> Show alert again </Button> ); } return ( <Alert color="success" showIcon className="max-w-md" onDismiss={() => setOpen(false)} > <AlertTitle>Domain verified</AlertTitle> <AlertDescription> acme.com is connected. SSL certificates are issued automatically. </AlertDescription> </Alert> );}
Render the alert conditionally after an async action. A danger alert has role="alert", so screen readers announce it as soon as it appears, without moving focus. A success or info result gets role="status" and is announced politely instead.
import { useState } from "react";import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";import { Button } from "@/components/ui/button";type Status = "idle" | "testing" | "failed";export default function AlertLive() { const [status, setStatus] = useState<Status>("idle"); async function test() { setStatus("testing"); await new Promise((r) => setTimeout(r, 1200)); setStatus("failed"); } return ( <div className="flex w-full max-w-md flex-col items-start gap-3"> <Button variant="outline" isPending={status === "testing"} onPress={test}> Test connection </Button> {status === "failed" && ( <Alert color="danger" showIcon onDismiss={() => setStatus("idle")}> <AlertTitle>Connection refused</AlertTitle> <AlertDescription> db.internal:5432 didn't respond. Check the host and that your IP is on the allow list. </AlertDescription> </Alert> )} </div> );}
The shadcn variant names still work: default maps to outline + neutral, and destructive maps to outline + danger. An explicit color wins over the alias.
Heads up!
You can add components to your app using the CLI.
Your session has expired
Please sign in again to continue.
import { TerminalIcon } from "lucide-react";import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";export default function AlertShadcn() { return ( <div className="flex w-full max-w-md flex-col gap-3"> <Alert variant="default" icon={<TerminalIcon />}> <AlertTitle>Heads up!</AlertTitle> <AlertDescription> You can add components to your app using the CLI. </AlertDescription> </Alert> <Alert variant="destructive" showIcon> <AlertTitle>Your session has expired</AlertTitle> <AlertDescription>Please sign in again to continue.</AlertDescription> </Alert> </div> );}
An accent warning with a Meter inside the content column and an upgrade action.
You're almost out of seats
New invitations will be blocked once all seats are taken.
9 of 10 seats
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";import { Button } from "@/components/ui/button";import { Meter } from "@/components/ui/meter";export default function AlertRecipePlanLimit() { return ( <Alert color="warning" variant="accent" showIcon className="max-w-lg" action={<Button size="sm">Add seats</Button>} > <AlertTitle>You're almost out of seats</AlertTitle> <AlertDescription> New invitations will be blocked once all seats are taken. </AlertDescription> <Meter aria-label="Seats used" value={9} maxValue={10} valueLabel="9 of 10 seats" color="warning" size="sm" className="mt-2 max-w-60" /> </Alert> );}
The root is a live region whose urgency follows color: danger and warning get role="alert" (assertive — announced immediately when inserted or changed); every other color gets role="status" (polite — announced when the user is idle). Legacy variant="destructive" resolves to danger, so it stays assertive.
An explicit role always wins. Use role="note" for static content that's on the page from the start and shouldn't be announced, or role="alert" to make a non-danger tone urgent.
Live regions announce content that changes after they're in the DOM. For an alert that appears in response to an action, render it conditionally (as above) rather than toggling its text inside an always-present element.
Alerts never move focus. If the user must act, put the action in the alert and consider a Dialog instead.
Icons are decorative. The title (or description) must carry the meaning on its own; don't rely on color alone.
The dismiss button is a React Aria Button labelled "Dismiss", with a keyboard-only focus ring.
Avoid auto-dismissing alerts; users of assistive tech may not have finished reading them.