Dismissible Alert Set: Success, Warning, Error and Info

Four inline alerts in one file: green for success, amber for a warning, red for an error and blue for information. Each has a round icon, a short title, one sentence of detail, an action link and a close button. Closing an alert removes it, and a small bar appears underneath with a button to bring them all back. The alerts are full width inside their container, so they wrap cleanly on phones with the close button staying in the top corner.

Code

import { useState } from "react";

const alerts = [
  {
    id: "success",
    role: "status",
    title: "Payout sent",
    body: "$2,480.00 is on its way to the account ending 4471. It should arrive by Friday.",
    action: "View payout",
    icon: "m8 12.5 2.8 2.8L16 9.5",
    box: "bg-emerald-50 ring-emerald-200",
    badge: "bg-emerald-600",
    heading: "text-emerald-950",
    text: "text-emerald-900",
    hover: "hover:bg-emerald-100 focus-visible:ring-emerald-700",
  },
  {
    id: "warning",
    role: "alert",
    title: "Your card expires this month",
    body: "The Visa ending 0392 expires on 31 March. Update it to keep your plan running.",
    action: "Update card",
    icon: "M12 7.5v5.5m0 3.4v.1",
    box: "bg-amber-50 ring-amber-200",
    badge: "bg-amber-600",
    heading: "text-amber-950",
    text: "text-amber-900",
    hover: "hover:bg-amber-100 focus-visible:ring-amber-700",
  },
  {
    id: "error",
    role: "alert",
    title: "Import stopped at row 214",
    body: "The date in column D is not in a format we can read. Nothing after that row was saved.",
    action: "Download error report",
    icon: "m8.8 8.8 6.4 6.4m0-6.4-6.4 6.4",
    box: "bg-rose-50 ring-rose-200",
    badge: "bg-rose-600",
    heading: "text-rose-950",
    text: "text-rose-900",
    hover: "hover:bg-rose-100 focus-visible:ring-rose-700",
  },
  {
    id: "info",
    role: "status",
    title: "Maintenance on Sunday",
    body: "Reports will be read-only from 02:00 to 03:30 UTC while we move to faster storage.",
    action: "See what changes",
    icon: "M12 11v5.5m0-9v.1",
    box: "bg-sky-50 ring-sky-200",
    badge: "bg-sky-600",
    heading: "text-sky-950",
    text: "text-sky-900",
    hover: "hover:bg-sky-100 focus-visible:ring-sky-700",
  },
];

export default function DismissibleAlertSetSuccessWarningErrorInfo() {
  const [dismissed, setDismissed] = useState<string[]>([]);
  const visible = alerts.filter((alert) => !dismissed.includes(alert.id));

  return (
    <div className="bg-white px-4 py-10 sm:px-8 sm:py-14">
      <div className="mx-auto max-w-2xl space-y-3">
        {visible.map((alert) => (
          <div key={alert.id} role={alert.role} className={`flex items-start gap-3 rounded-xl py-3 pl-4 pr-2 ring-1 ring-inset sm:gap-3.5 ${alert.box}`}>
            <span className={`mt-1 flex h-6 w-6 flex-none items-center justify-center rounded-full text-white ${alert.badge}`}>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-6 w-6"><path d={alert.icon} /></svg>
            </span>
            <div className="min-w-0 flex-1 py-1">
              <p className={`text-sm font-semibold ${alert.heading}`}>{alert.title}</p>
              <p className={`mt-0.5 text-sm leading-relaxed ${alert.text}`}>{alert.body}</p>
              <a href="#" className={`-ml-2 mt-1 inline-flex min-h-[40px] items-center gap-1 rounded-lg px-2 text-sm font-semibold underline decoration-1 underline-offset-4 transition focus:outline-none focus-visible:ring-2 ${alert.heading} ${alert.hover}`}>
                {alert.action}
              </a>
            </div>
            <button
              type="button"
              onClick={() => setDismissed([...dismissed, alert.id])}
              aria-label={`Dismiss: ${alert.title}`}
              className={`flex h-10 w-10 flex-none items-center justify-center rounded-lg transition focus:outline-none focus-visible:ring-2 ${alert.text} ${alert.hover}`}
            >
              <svg viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" aria-hidden="true" className="h-4 w-4"><path d="m5 5 10 10M15 5 5 15" /></svg>
            </button>
          </div>
        ))}

        {dismissed.length > 0 && (
          <div className="flex flex-wrap items-center justify-between gap-2 rounded-xl border border-dashed border-slate-300 py-2 pl-4 pr-2">
            <p className="text-sm text-slate-600">
              {visible.length === 0 ? "You are all caught up." : `${dismissed.length} dismissed.`}
            </p>
            <button type="button" onClick={() => setDismissed([])} className="min-h-[40px] rounded-lg px-3 text-sm font-semibold text-slate-900 transition hover:bg-slate-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-slate-900">
              Show all again
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/dismissible-alert-set-success-warning-error-info.json

When to use it

How to customize it

Accessibility

Questions

How do I make a Tailwind alert dismissible?

Store which alerts are closed in React state and leave them out when rendering. Here `dismissed` is an array of ids, the close button adds the id to it, and the list is filtered before it is drawn. Tailwind has no behaviour of its own, so the state is what does the work.

What is the difference between an alert and a toast?

An alert sits in the page layout, close to what it is about, and usually stays until dismissed. A toast floats over the page in a corner and leaves by itself. Use alerts for things people must read and toasts for quick confirmations.

When should an alert not have a close button?

When the problem still needs fixing. If a card has been declined or a required setting is missing, hiding the message does not solve it, so keep the alert in place until the cause is gone.

How do I remember that an alert was dismissed?

Save the dismissed ids somewhere that lasts, such as the user's profile on your server or the browser's local storage, and use that as the starting value of the state when the page loads.

Which colours should I use for alert types?

Green for success, amber or yellow for warnings, red for errors and blue for neutral information are what most people expect. Pair each with a clear title so nobody has to decode the colour.

Related: Dark Toast Notification Stack with Undo and Progress, Delete Confirmation Dialog with Summary, Button Set: Primary, Secondary, Ghost, Icon and Loading.

More alerts and feedback or all free UI components. Start for free to have a full site built around it.

More in Feedback

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap