Glass Alert
Original · freeA frosted-glass alert over a subtle gradient.
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-glass.json"use client";
import { useState, type ReactElement } from "react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
type GlassAlert = {
id: string;
tone: "info" | "success" | "warning" | "danger";
title: string;
body: string;
action: string;
};
const SEED: GlassAlert[] = [
{
id: "deploy",
tone: "success",
title: "Deploy shipped to production",
body: "Build 4.12.0 rolled out to all 3 regions in 47s. Error rate is flat at 0.02%.",
action: "View release",
},
{
id: "billing",
tone: "warning",
title: "Card expires this month",
body: "The Visa ending 4471 on your workspace expires 07/26. Update it to avoid a failed renewal.",
action: "Update payment",
},
{
id: "invite",
tone: "info",
title: "3 teammates are waiting",
body: "Priya, Marcus and Wei requested access to the Growth workspace 2 days ago.",
action: "Review requests",
},
{
id: "quota",
tone: "danger",
title: "API quota nearly exhausted",
body: "You have used 94% of this month's 500k requests. Throttling begins at 100%.",
action: "Raise limit",
},
];
const TONE: Record<
GlassAlert["tone"],
{
ring: string;
glow: string;
dot: string;
chip: string;
label: string;
icon: ReactElement;
}
> = {
info: {
ring: "ring-sky-400/40 dark:ring-sky-300/25",
glow: "from-sky-400/25 via-sky-300/10",
dot: "bg-sky-500 dark:bg-sky-400",
chip:
"bg-sky-500/15 text-sky-700 ring-sky-500/25 dark:bg-sky-400/15 dark:text-sky-200 dark:ring-sky-300/25",
label: "Info",
icon: (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9h.01M11 12h1v4h1m-1-10a9 9 0 100 18 9 9 0 000-18z"
/>
),
},
success: {
ring: "ring-emerald-400/40 dark:ring-emerald-300/25",
glow: "from-emerald-400/25 via-emerald-300/10",
dot: "bg-emerald-500 dark:bg-emerald-400",
chip:
"bg-emerald-500/15 text-emerald-700 ring-emerald-500/25 dark:bg-emerald-400/15 dark:text-emerald-200 dark:ring-emerald-300/25",
label: "Success",
icon: (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 12.5l2 2 4-4.5M12 3a9 9 0 100 18 9 9 0 000-18z"
/>
),
},
warning: {
ring: "ring-amber-400/40 dark:ring-amber-300/25",
glow: "from-amber-400/25 via-amber-300/10",
dot: "bg-amber-500 dark:bg-amber-400",
chip:
"bg-amber-500/15 text-amber-700 ring-amber-500/25 dark:bg-amber-400/15 dark:text-amber-200 dark:ring-amber-300/25",
label: "Warning",
icon: (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 9v4m0 3h.01M10.3 4.2L2.6 17.5A2 2 0 004.3 20.5h15.4a2 2 0 001.7-3L13.7 4.2a2 2 0 00-3.4 0z"
/>
),
},
danger: {
ring: "ring-rose-400/40 dark:ring-rose-300/25",
glow: "from-rose-400/25 via-rose-300/10",
dot: "bg-rose-500 dark:bg-rose-400",
chip:
"bg-rose-500/15 text-rose-700 ring-rose-500/25 dark:bg-rose-400/15 dark:text-rose-200 dark:ring-rose-300/25",
label: "Critical",
icon: (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 8v4m0 4h.01M12 3a9 9 0 100 18 9 9 0 000-18z"
/>
),
},
};
export default function AlertGlass() {
const prefersReduced = useReducedMotion();
const [alerts, setAlerts] = useState<GlassAlert[]>(SEED);
const [resolved, setResolved] = useState<string | null>(null);
const dismiss = (id: string) =>
setAlerts((prev) => prev.filter((a) => a.id !== id));
const act = (a: GlassAlert) => {
setResolved(a.action);
dismiss(a.id);
window.setTimeout(() => setResolved(null), 2600);
};
const reset = () => {
setResolved(null);
setAlerts(SEED);
};
return (
<section className="relative w-full overflow-hidden bg-slate-50 px-5 py-20 sm:px-8 sm:py-24 dark:bg-slate-950">
<style>{`
@keyframes agls_drift {
0% { transform: translate3d(0,0,0) scale(1); }
50% { transform: translate3d(4%,-3%,0) scale(1.08); }
100% { transform: translate3d(0,0,0) scale(1); }
}
@keyframes agls_sheen {
0% { transform: translateX(-120%); }
60% { transform: translateX(220%); }
100% { transform: translateX(220%); }
}
@media (prefers-reduced-motion: reduce) {
.agls_drift, .agls_sheen { animation: none !important; }
}
`}</style>
{/* gradient backdrop */}
<div aria-hidden className="pointer-events-none absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-br from-indigo-100 via-slate-50 to-sky-100 dark:from-indigo-950/60 dark:via-slate-950 dark:to-sky-950/50" />
<div className="agls_drift absolute -left-24 top-0 h-72 w-72 rounded-full bg-violet-400/40 blur-3xl dark:bg-violet-600/30" style={{ animation: "agls_drift 18s ease-in-out infinite" }} />
<div className="agls_drift absolute right-0 top-24 h-80 w-80 rounded-full bg-sky-400/40 blur-3xl dark:bg-sky-600/25" style={{ animation: "agls_drift 22s ease-in-out infinite reverse" }} />
<div className="agls_drift absolute bottom-0 left-1/3 h-64 w-64 rounded-full bg-emerald-300/40 blur-3xl dark:bg-emerald-600/20" style={{ animation: "agls_drift 26s ease-in-out infinite" }} />
</div>
<div className="relative mx-auto flex max-w-2xl flex-col gap-4">
<header className="mb-2 flex items-end justify-between gap-4">
<div>
<h2 className="text-lg font-semibold tracking-tight text-slate-900 dark:text-white">
Notifications
</h2>
<p className="text-sm text-slate-600 dark:text-slate-400">
{alerts.length > 0
? `${alerts.length} item${alerts.length > 1 ? "s" : ""} need your attention`
: "You're all caught up."}
</p>
</div>
<button
type="button"
onClick={reset}
className="rounded-full px-3 py-1.5 text-xs font-medium text-slate-600 ring-1 ring-slate-300/70 transition hover:bg-white/60 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-300 dark:ring-white/15 dark:hover:bg-white/10 dark:hover:text-white"
>
Restore all
</button>
</header>
<div
className="sr-only"
role="status"
aria-live="polite"
>
{resolved ? `${resolved} action confirmed.` : ""}
</div>
<AnimatePresence mode="popLayout">
{resolved && (
<motion.div
key="toast"
layout={!prefersReduced}
initial={prefersReduced ? false : { opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={prefersReduced ? { opacity: 0 } : { opacity: 0, y: -8 }}
className="flex items-center gap-2 rounded-2xl border border-emerald-400/30 bg-emerald-400/15 px-4 py-3 text-sm font-medium text-emerald-800 backdrop-blur-md dark:text-emerald-200"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} className="h-4 w-4 shrink-0">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.5l2 2 4-4.5" />
</svg>
Done — “{resolved}” confirmed.
</motion.div>
)}
{alerts.map((a) => {
const t = TONE[a.tone];
return (
<motion.article
key={a.id}
layout={!prefersReduced}
initial={prefersReduced ? false : { opacity: 0, y: 12, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={
prefersReduced
? { opacity: 0 }
: { opacity: 0, x: 40, scale: 0.96, transition: { duration: 0.22 } }
}
transition={{ type: "spring", stiffness: 380, damping: 32 }}
role="alert"
aria-label={`${t.label}: ${a.title}`}
className={`group relative overflow-hidden rounded-2xl border border-white/40 bg-white/50 p-4 shadow-lg shadow-slate-900/5 ring-1 backdrop-blur-xl sm:p-5 dark:border-white/10 dark:bg-white/5 dark:shadow-black/30 ${t.ring}`}
>
{/* tone glow */}
<div
aria-hidden
className={`pointer-events-none absolute -left-16 -top-16 h-40 w-40 rounded-full bg-gradient-to-br to-transparent blur-2xl ${t.glow}`}
/>
{/* sheen */}
<div aria-hidden className="pointer-events-none absolute inset-0 overflow-hidden rounded-2xl">
<div
className="agls_sheen absolute inset-y-0 -left-1/3 w-1/3 -skew-x-12 bg-gradient-to-r from-transparent via-white/40 to-transparent opacity-0 group-hover:opacity-100 dark:via-white/15"
style={{ animation: prefersReduced ? "none" : "agls_sheen 1.4s ease-in-out" }}
/>
</div>
<div className="relative flex items-start gap-3.5">
<span
aria-hidden
className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-white/70 ring-1 ring-white/60 dark:bg-white/10 dark:ring-white/10"
>
<span className="relative flex h-2.5 w-2.5">
<span className={`absolute inline-flex h-full w-full rounded-full opacity-60 ${t.dot} ${prefersReduced ? "" : "animate-ping"}`} />
<span className={`relative inline-flex h-2.5 w-2.5 rounded-full ${t.dot}`} />
</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.7} className="absolute h-5 w-5 text-slate-700 opacity-0 transition-opacity group-hover:opacity-100 dark:text-slate-200">
{t.icon}
</svg>
</span>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide ring-1 ${t.chip}`}>
{t.label}
</span>
<h3 className="truncate text-sm font-semibold text-slate-900 dark:text-white">
{a.title}
</h3>
</div>
<p className="mt-1 text-sm leading-relaxed text-slate-600 dark:text-slate-300">
{a.body}
</p>
<div className="mt-3 flex items-center gap-2">
<button
type="button"
onClick={() => act(a)}
className="rounded-lg bg-slate-900/90 px-3 py-1.5 text-xs font-semibold text-white shadow-sm transition hover:bg-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
>
{a.action}
</button>
<button
type="button"
onClick={() => dismiss(a.id)}
className="rounded-lg px-3 py-1.5 text-xs font-medium text-slate-600 transition hover:bg-white/70 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white"
>
Dismiss
</button>
</div>
</div>
<button
type="button"
onClick={() => dismiss(a.id)}
aria-label={`Dismiss: ${a.title}`}
className="-mr-1 -mt-1 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-slate-500 transition hover:bg-white/70 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-400 dark:hover:bg-white/10 dark:hover:text-white"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} className="h-4 w-4">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 6l12 12M18 6L6 18" />
</svg>
</button>
</div>
</motion.article>
);
})}
</AnimatePresence>
{alerts.length === 0 && !resolved && (
<motion.div
initial={prefersReduced ? false : { opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
className="flex flex-col items-center gap-3 rounded-2xl border border-white/40 bg-white/40 px-6 py-12 text-center backdrop-blur-xl dark:border-white/10 dark:bg-white/5"
>
<span className="flex h-11 w-11 items-center justify-center rounded-full bg-emerald-500/15 text-emerald-600 ring-1 ring-emerald-500/25 dark:text-emerald-300">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} className="h-5 w-5">
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
</span>
<p className="text-sm font-medium text-slate-700 dark:text-slate-200">
Inbox zero. Nothing needs you right now.
</p>
<button
type="button"
onClick={reset}
className="rounded-full bg-slate-900/90 px-4 py-2 text-xs font-semibold text-white transition hover:bg-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-100"
>
Restore notifications
</button>
</motion.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.

