List Alert
Original · freeAn alert containing a short bullet list of items.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/alert-list.json"use client";
import { useState } from "react";
type Variant = "info" | "success" | "warning" | "danger";
type AlertItem = {
id: string;
variant: Variant;
title: string;
items: string[];
};
type VariantStyle = {
wrap: string;
iconWrap: string;
title: string;
body: string;
marker: string;
ring: string;
icon: JSX.Element;
};
const VARIANTS: Record<Variant, VariantStyle> = {
info: {
wrap: "border-sky-200 bg-sky-50 dark:border-sky-900/60 dark:bg-sky-950/40",
iconWrap: "bg-sky-100 text-sky-600 dark:bg-sky-900/60 dark:text-sky-300",
title: "text-sky-900 dark:text-sky-100",
body: "text-sky-800/90 dark:text-sky-200/80",
marker: "text-sky-500 dark:text-sky-400",
ring: "focus-visible:ring-sky-500",
icon: (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9h.01M11 12h1v4h1m8-4a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
),
},
success: {
wrap: "border-emerald-200 bg-emerald-50 dark:border-emerald-900/60 dark:bg-emerald-950/40",
iconWrap: "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/60 dark:text-emerald-300",
title: "text-emerald-900 dark:text-emerald-100",
body: "text-emerald-800/90 dark:text-emerald-200/80",
marker: "text-emerald-500 dark:text-emerald-400",
ring: "focus-visible:ring-emerald-500",
icon: (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m9 12.75 2 2 4-4.5m6 1.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
/>
),
},
warning: {
wrap: "border-amber-200 bg-amber-50 dark:border-amber-900/60 dark:bg-amber-950/40",
iconWrap: "bg-amber-100 text-amber-600 dark:bg-amber-900/60 dark:text-amber-300",
title: "text-amber-900 dark:text-amber-100",
body: "text-amber-800/90 dark:text-amber-200/80",
marker: "text-amber-500 dark:text-amber-400",
ring: "focus-visible:ring-amber-500",
icon: (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v3.75m0 3.75h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"
/>
),
},
danger: {
wrap: "border-rose-200 bg-rose-50 dark:border-rose-900/60 dark:bg-rose-950/40",
iconWrap: "bg-rose-100 text-rose-600 dark:bg-rose-900/60 dark:text-rose-300",
title: "text-rose-900 dark:text-rose-100",
body: "text-rose-800/90 dark:text-rose-200/80",
marker: "text-rose-500 dark:text-rose-400",
ring: "focus-visible:ring-rose-500",
icon: (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.01"
/>
),
},
};
const INITIAL_ALERTS: AlertItem[] = [
{
id: "deploy",
variant: "success",
title: "Deployment finished",
items: [
"Build passed in 42s across 3 regions.",
"Database migration 0047 applied cleanly.",
"Cache purged for 12,480 edge nodes.",
],
},
{
id: "review",
variant: "warning",
title: "Before you publish this page",
items: [
"Meta description is 41 characters over the limit.",
"Two images are missing descriptive alt text.",
"The canonical URL points to a staging domain.",
],
},
{
id: "billing",
variant: "danger",
title: "Payment could not be processed",
items: [
"The card ending in 4218 was declined by the issuer.",
"Your plan renews in 3 days on July 17, 2026.",
"Automations will pause if payment is not updated.",
],
},
{
id: "tips",
variant: "info",
title: "New in this release",
items: [
"Draft autosave now runs every 5 seconds.",
"Keyboard shortcut ⌘K opens the command palette.",
"Exports support CSV, JSON, and Parquet.",
],
},
];
export default function AlertList() {
const [alerts, setAlerts] = useState<AlertItem[]>(INITIAL_ALERTS);
const [leaving, setLeaving] = useState<string[]>([]);
const dismiss = (id: string) => {
setLeaving((prev) => (prev.includes(id) ? prev : [...prev, id]));
};
const finalize = (id: string) => {
setAlerts((prev) => prev.filter((a) => a.id !== id));
setLeaving((prev) => prev.filter((x) => x !== id));
};
const reset = () => {
setLeaving([]);
setAlerts(INITIAL_ALERTS);
};
return (
<section className="relative w-full bg-white px-4 py-16 dark:bg-neutral-950 sm:px-6 sm:py-24">
<style>{`
@keyframes alertlist-in {
from { opacity: 0; transform: translateY(10px) scale(0.985); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes alertlist-out {
from { opacity: 1; transform: translateX(0); max-height: 40rem; }
to { opacity: 0; transform: translateX(28px); max-height: 0; margin-bottom: -1rem; }
}
.alertlist-enter { animation: alertlist-in 0.45s cubic-bezier(0.22, 1, 0.36, 1) both; }
.alertlist-leave { animation: alertlist-out 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; overflow: hidden; }
@media (prefers-reduced-motion: reduce) {
.alertlist-enter, .alertlist-leave { animation-duration: 0.01ms !important; }
}
`}</style>
<div className="mx-auto max-w-2xl">
<div className="mb-8 flex items-end justify-between gap-4">
<div>
<h2 className="text-2xl font-semibold tracking-tight text-neutral-900 dark:text-neutral-50">
Notifications
</h2>
<p className="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
{alerts.length > 0
? `${alerts.length} item${alerts.length > 1 ? "s" : ""} need your attention`
: "You're all caught up."}
</p>
</div>
{alerts.length < INITIAL_ALERTS.length && (
<button
type="button"
onClick={reset}
className="shrink-0 rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm font-medium text-neutral-700 transition-colors hover:bg-neutral-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-200 dark:hover:bg-neutral-800 dark:focus-visible:ring-offset-neutral-950"
>
Restore all
</button>
)}
</div>
<div className="flex flex-col gap-4">
{alerts.map((alert) => {
const v = VARIANTS[alert.variant];
const isLeaving = leaving.includes(alert.id);
const titleId = `alertlist-${alert.id}-title`;
return (
<div
key={alert.id}
role="alert"
aria-labelledby={titleId}
aria-hidden={isLeaving}
onAnimationEnd={(e) => {
if (isLeaving && e.animationName === "alertlist-out") {
finalize(alert.id);
}
}}
className={`${isLeaving ? "alertlist-leave" : "alertlist-enter"} rounded-2xl border p-5 shadow-sm ${v.wrap}`}
>
<div className="flex items-start gap-4">
<span
className={`mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-full ${v.iconWrap}`}
aria-hidden="true"
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.75}
className="h-5 w-5"
>
{v.icon}
</svg>
</span>
<div className="min-w-0 flex-1">
<h3
id={titleId}
className={`text-sm font-semibold ${v.title}`}
>
{alert.title}
</h3>
<ul className={`mt-2 space-y-1.5 text-sm ${v.body}`}>
{alert.items.map((item, i) => (
<li key={i} className="flex gap-2.5">
<svg
viewBox="0 0 16 16"
fill="currentColor"
className={`mt-[7px] h-1.5 w-1.5 shrink-0 ${v.marker}`}
aria-hidden="true"
>
<circle cx="8" cy="8" r="8" />
</svg>
<span className="leading-relaxed">{item}</span>
</li>
))}
</ul>
</div>
<button
type="button"
onClick={() => dismiss(alert.id)}
aria-label={`Dismiss: ${alert.title}`}
className={`-mr-1 -mt-1 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-neutral-400 transition-colors hover:bg-black/5 hover:text-neutral-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent dark:text-neutral-500 dark:hover:bg-white/10 dark:hover:text-neutral-200 ${v.ring}`}
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
className="h-4 w-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 6l12 12M18 6L6 18"
/>
</svg>
</button>
</div>
</div>
);
})}
{alerts.length === 0 && (
<div className="alertlist-enter rounded-2xl border border-dashed border-neutral-300 bg-neutral-50 px-6 py-12 text-center dark:border-neutral-800 dark:bg-neutral-900/40">
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-200">
Inbox zero.
</p>
<p className="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
Every alert has been cleared.
</p>
<button
type="button"
onClick={reset}
className="mt-5 rounded-lg bg-neutral-900 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-neutral-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-500 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-50 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200 dark:focus-visible:ring-offset-neutral-950"
>
Bring them back
</button>
</div>
)}
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Soft Variants Alert
OriginalSoft-tinted alerts: success, info, warning and error, each with an icon.

Solid Variants Alert
OriginalSolid-colour alerts across the four states.

Outline Variants Alert
OriginalOutlined alerts across the four states.

Left Accent Alert
OriginalAlerts with a coloured left accent bar and icon.

Dismissible Alert
OriginalDismissible alerts with a working close button.

With Action Alert
OriginalAn alert with title, body and action buttons.
Icon Large Alert
OriginalAn alert led by a large circular icon.

Banner Top Alert
OriginalA full-width top announcement banner, dismissible.

Toast Stack Alert
OriginalA stack of dismissible toasts in a corner.

Toast Slide Alert
OriginalA toast that slides in with an icon and auto-dismiss progress.

Progress Alert
OriginalAn alert with a countdown progress bar.

Gradient Alert
OriginalA gradient promo banner alert with a call to action.

