Soft Variants Alert
Original · freeSoft-tinted alerts: success, info, warning and error, each with an icon.
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-soft-variants.json"use client";
import { useState } from "react";
type Variant = "success" | "info" | "warning" | "error";
type AlertItem = {
id: string;
variant: Variant;
title: string;
message: string;
action: string;
};
const ALERTS: AlertItem[] = [
{
id: "success",
variant: "success",
title: "Payment received",
message: "Your invoice #INV-2094 for $1,280.00 was paid. A receipt is on its way to your inbox.",
action: "View receipt",
},
{
id: "info",
variant: "info",
title: "Scheduled maintenance",
message: "We'll be upgrading our servers on Sunday, 02:00-04:00 UTC. Expect brief interruptions to the dashboard.",
action: "Read details",
},
{
id: "warning",
variant: "warning",
title: "Storage almost full",
message: "You've used 92% of your 50 GB plan. Uploads will be paused once you hit the limit.",
action: "Upgrade plan",
},
{
id: "error",
variant: "error",
title: "Deployment failed",
message: "Build step 'compile' exited with code 1. Check the logs to see which module broke the pipeline.",
action: "Open logs",
},
];
const STYLES: Record<
Variant,
{
container: string;
iconWrap: string;
title: string;
body: string;
action: string;
close: string;
ring: string;
}
> = {
success: {
container:
"border-emerald-200/80 bg-emerald-50 dark:border-emerald-500/25 dark:bg-emerald-500/10",
iconWrap:
"bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300",
title: "text-emerald-900 dark:text-emerald-100",
body: "text-emerald-800/90 dark:text-emerald-200/80",
action:
"text-emerald-700 hover:text-emerald-900 dark:text-emerald-300 dark:hover:text-emerald-100",
close:
"text-emerald-600/70 hover:bg-emerald-100 hover:text-emerald-800 dark:text-emerald-300/70 dark:hover:bg-emerald-500/20 dark:hover:text-emerald-100",
ring: "focus-visible:ring-emerald-500/60",
},
info: {
container:
"border-sky-200/80 bg-sky-50 dark:border-sky-500/25 dark:bg-sky-500/10",
iconWrap: "bg-sky-100 text-sky-700 dark:bg-sky-500/20 dark:text-sky-300",
title: "text-sky-900 dark:text-sky-100",
body: "text-sky-800/90 dark:text-sky-200/80",
action:
"text-sky-700 hover:text-sky-900 dark:text-sky-300 dark:hover:text-sky-100",
close:
"text-sky-600/70 hover:bg-sky-100 hover:text-sky-800 dark:text-sky-300/70 dark:hover:bg-sky-500/20 dark:hover:text-sky-100",
ring: "focus-visible:ring-sky-500/60",
},
warning: {
container:
"border-amber-200/80 bg-amber-50 dark:border-amber-500/25 dark:bg-amber-500/10",
iconWrap:
"bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300",
title: "text-amber-900 dark:text-amber-100",
body: "text-amber-800/90 dark:text-amber-200/80",
action:
"text-amber-700 hover:text-amber-900 dark:text-amber-300 dark:hover:text-amber-100",
close:
"text-amber-600/70 hover:bg-amber-100 hover:text-amber-800 dark:text-amber-300/70 dark:hover:bg-amber-500/20 dark:hover:text-amber-100",
ring: "focus-visible:ring-amber-500/60",
},
error: {
container:
"border-rose-200/80 bg-rose-50 dark:border-rose-500/25 dark:bg-rose-500/10",
iconWrap: "bg-rose-100 text-rose-700 dark:bg-rose-500/20 dark:text-rose-300",
title: "text-rose-900 dark:text-rose-100",
body: "text-rose-800/90 dark:text-rose-200/80",
action:
"text-rose-700 hover:text-rose-900 dark:text-rose-300 dark:hover:text-rose-100",
close:
"text-rose-600/70 hover:bg-rose-100 hover:text-rose-800 dark:text-rose-300/70 dark:hover:bg-rose-500/20 dark:hover:text-rose-100",
ring: "focus-visible:ring-rose-500/60",
},
};
function AlertIcon({ variant }: { variant: Variant }) {
const common = {
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: 2,
strokeLinecap: "round" as const,
strokeLinejoin: "round" as const,
className: "h-5 w-5",
"aria-hidden": true,
};
if (variant === "success") {
return (
<svg {...common}>
<path d="M20 6 9 17l-5-5" />
</svg>
);
}
if (variant === "info") {
return (
<svg {...common}>
<circle cx="12" cy="12" r="9" />
<path d="M12 11v5" />
<path d="M12 8h.01" />
</svg>
);
}
if (variant === "warning") {
return (
<svg {...common}>
<path d="M10.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" />
<path d="M12 9v4" />
<path d="M12 17h.01" />
</svg>
);
}
return (
<svg {...common}>
<circle cx="12" cy="12" r="9" />
<path d="m15 9-6 6" />
<path d="m9 9 6 6" />
</svg>
);
}
export default function AlertSoftVariants() {
const [dismissed, setDismissed] = useState<Record<string, boolean>>({});
const [statusText, setStatusText] = useState<string>("");
const visible = ALERTS.filter((a) => !dismissed[a.id]);
const dismiss = (item: AlertItem) => {
setDismissed((prev) => ({ ...prev, [item.id]: true }));
setStatusText(`${item.title} alert dismissed.`);
};
const restore = () => {
setDismissed({});
setStatusText("All alerts restored.");
};
return (
<section className="relative w-full bg-white px-4 py-20 sm:px-6 lg:px-8 dark:bg-zinc-950">
<style>{`
@keyframes asv-enter {
from { opacity: 0; transform: translateY(8px) scale(0.985); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.asv-item { animation: asv-enter 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; }
@media (prefers-reduced-motion: reduce) {
.asv-item { animation: none; }
}
`}</style>
<div className="mx-auto max-w-2xl">
<div className="mb-8 flex flex-wrap items-end justify-between gap-4">
<div>
<h2 className="text-2xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-50">
Notifications
</h2>
<p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
Soft-tinted alerts for every state.
</p>
</div>
{visible.length < ALERTS.length ? (
<button
type="button"
onClick={restore}
className="rounded-lg border border-zinc-200 bg-white px-3 py-1.5 text-sm font-medium text-zinc-700 transition-colors hover:bg-zinc-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/60 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800"
>
Restore all
</button>
) : null}
</div>
<div className="space-y-3">
{visible.map((item) => {
const s = STYLES[item.variant];
return (
<div
key={item.id}
role="alert"
className={`asv-item flex items-start gap-3 rounded-xl border p-4 ${s.container}`}
>
<span
className={`mt-0.5 flex h-9 w-9 flex-none items-center justify-center rounded-lg ${s.iconWrap}`}
>
<AlertIcon variant={item.variant} />
</span>
<div className="min-w-0 flex-1">
<p className={`text-sm font-semibold ${s.title}`}>
{item.title}
</p>
<p className={`mt-1 text-sm leading-relaxed ${s.body}`}>
{item.message}
</p>
<button
type="button"
onClick={() =>
setStatusText(`${item.action} selected for "${item.title}".`)
}
className={`mt-2 rounded text-sm font-semibold underline-offset-2 transition-colors hover:underline focus:outline-none focus-visible:ring-2 focus-visible:rounded ${s.action} ${s.ring}`}
>
{item.action}
</button>
</div>
<button
type="button"
onClick={() => dismiss(item)}
aria-label={`Dismiss ${item.title} alert`}
className={`-mr-1 -mt-1 flex h-8 w-8 flex-none items-center justify-center rounded-lg transition-colors focus:outline-none focus-visible:ring-2 ${s.close} ${s.ring}`}
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
aria-hidden="true"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</div>
);
})}
{visible.length === 0 ? (
<div className="rounded-xl border border-dashed border-zinc-300 bg-zinc-50 p-8 text-center dark:border-zinc-700 dark:bg-zinc-900/50">
<p className="text-sm font-medium text-zinc-600 dark:text-zinc-300">
You're all caught up.
</p>
<button
type="button"
onClick={restore}
className="mt-3 rounded-lg bg-zinc-900 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-zinc-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/60 dark:bg-white dark:text-zinc-900 dark:hover:bg-zinc-200"
>
Restore alerts
</button>
</div>
) : null}
</div>
<p className="sr-only" role="status" aria-live="polite">
{statusText}
</p>
</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 →
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.

Inline Form Alert
OriginalAn alert containing an inline email input and button.

