Alert

An alert is a message that stays in the page: a note, a success, a warning or an error.

Heads up
You can add components to your project at any time.
Saved
Your changes are live.
Almost full
You have used 90% of your storage.
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";

export default function AlertDemo() {
  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <Alert>
        <AlertTitle>Heads up</AlertTitle>
        <AlertDescription>You can add components to your project at any time.</AlertDescription>
      </Alert>
      <Alert variant="success">
        <AlertTitle>Saved</AlertTitle>
        <AlertDescription>Your changes are live.</AlertDescription>
      </Alert>
      <Alert variant="warning">
        <AlertTitle>Almost full</AlertTitle>
        <AlertDescription>You have used 90% of your storage.</AlertDescription>
      </Alert>
      <Alert variant="destructive">
        <AlertTitle>Payment failed</AlertTitle>
        <AlertDescription>Your card was declined. Try a different card.</AlertDescription>
      </Alert>
    </div>
  );
}

The code

The component is a single file, read from the repository when the site is built. Press Show code to see all of it.

import type { ComponentProps } from "react";
import { CircleAlert, CircleCheck, Info, TriangleAlert, type LucideIcon } from "lucide-react";
import { cx } from "@/lib/cx";

type Variant = "default" | "success" | "warning" | "destructive";

// Each tone is a soft wash (10% fill, 30% border) with a full-strength icon, as the theming
// rules ask. Destructive is announced at once (role alert); the others politely (role status).
const variants: Record<Variant, { box: string; icon: string; Icon: LucideIcon; role: "status" | "alert" }> = {
  default: { box: "border-border bg-surface", icon: "text-text", Icon: Info, role: "status" },
  success: { box: "border-accent/30 bg-accent/10", icon: "text-accent", Icon: CircleCheck, role: "status" },
  warning: { box: "border-warning/30 bg-warning/10", icon: "text-warning", Icon: TriangleAlert, role: "status" },
  destructive: { box: "border-danger/30 bg-danger/10", icon: "text-danger", Icon: CircleAlert, role: "alert" },
};

export function Alert({
  variant = "default",
  className,
  children,
  ...props
}: ComponentProps<"div"> & { variant?: Variant }) {
  const { box, icon, Icon, role } = variants[variant];
  return (
    <div
      role={role}
      className={cx("grid grid-cols-[auto_1fr] items-start gap-x-3 rounded-lg border p-4 text-text", box, className)}
      {...props}
    >
      <Icon aria-hidden className={cx("mt-0.5 size-5", icon)} />
      <div className="flex flex-col gap-1">{children}</div>
    </div>
  );
}

export function AlertTitle({ className, ...props }: ComponentProps<"div">) {
  return <div className={cx("text-base leading-snug font-semibold", className)} {...props} />;
}

export function AlertDescription({ className, ...props }: ComponentProps<"div">) {
  return <div className={cx("text-sm text-text/70", className)} {...props} />;
}

Props

PropTypeDefaultWhat it does
variantdefault | success | warning | destructivedefaultThe tone of the message. It sets the color, the icon and how it is announced.
classNamestring—Extra classes, added after the alert's own.

Alert, AlertTitle and AlertDescription are plain divs, so they take every normal prop.

The thinking

An alert should say one thing, say how serious it is, and stay out of the way.

  • The icon says the tone: Each tone has its own icon, so the tone isn't carried by color alone.
  • Quiet color: A soft wash and a full-strength icon, never a solid fill. This is the low-emphasis pattern from Theming.
  • Static, not a toast: An alert sits in the page and stays. A message that appears and then fades away is a different component, a toast, which isn't built yet.

Interface design

  • Box: A 1px border, 16px of padding and the lg radius, 8px. The default is the surface fill with a hairline border.
  • Tones: Success, warning and destructive use a 10% fill and a 30% border in the tone's color.
  • Icon: 20px, in the tone's full-strength color, to the left of the text.
  • Text: The title is 16px semibold. The description is 14px, in the main text color at 70%.

Interaction

  • Not interactive: An alert has no hover, focus or pressed state. Put a link or a Button inside it if it needs an action.
  • How it is announced: A destructive alert is announced at once, as an alert. The others are announced politely, as a status.

Empathy

  • Words, not just color: Write a title that says what happened, like Payment failed, not just Error.
  • Contrast: The title is 13 to 15:1 against its tinted background, the description is above 7:1, and the icons are between 5:1 and 12:1.
  • Don't over-announce: Only the destructive tone interrupts a screen reader. Keep it for something that needs attention now.