Inline Form Alert
Original · freeAn alert containing an inline email input and button.
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-inline-form.json"use client";
import { useId, useRef, useState, type ChangeEvent, type FormEvent } from "react";
type Status = "idle" | "error" | "success";
export default function AlertInlineForm() {
const [email, setEmail] = useState("");
const [status, setStatus] = useState<Status>("idle");
const [dismissed, setDismissed] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const fieldId = useId();
const msgId = useId();
const isValidEmail = (value: string) =>
/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value.trim());
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (status === "success") return;
if (!isValidEmail(email)) {
setStatus("error");
inputRef.current?.focus();
return;
}
setStatus("success");
};
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setEmail(event.target.value);
if (status === "error") setStatus("idle");
};
if (dismissed) return null;
const errorText = status === "error";
const success = status === "success";
return (
<section className="relative w-full bg-white px-4 py-16 dark:bg-neutral-950 sm:py-24">
<style>{`
@keyframes aif-rise {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes aif-pop {
0% { opacity: 0; transform: scale(0.6); }
60% { transform: scale(1.08); }
100% { opacity: 1; transform: scale(1); }
}
@keyframes aif-shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-5px); }
40% { transform: translateX(5px); }
60% { transform: translateX(-3px); }
80% { transform: translateX(3px); }
}
.aif-rise { animation: aif-rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) both; }
.aif-pop { animation: aif-pop 0.4s cubic-bezier(0.22, 1, 0.36, 1) both; }
.aif-shake { animation: aif-shake 0.4s ease both; }
@media (prefers-reduced-motion: reduce) {
.aif-rise, .aif-pop, .aif-shake { animation: none !important; }
}
`}</style>
<div className="mx-auto max-w-2xl">
<div
role="region"
aria-label="Newsletter signup"
className="aif-rise relative overflow-hidden rounded-2xl border border-indigo-200 bg-indigo-50/70 p-5 shadow-sm ring-1 ring-inset ring-white/40 backdrop-blur dark:border-indigo-900/60 dark:bg-indigo-950/40 dark:ring-white/5 sm:p-6"
>
<span
aria-hidden="true"
className="pointer-events-none absolute -right-16 -top-16 h-40 w-40 rounded-full bg-violet-400/20 blur-3xl dark:bg-violet-500/20"
/>
<button
type="button"
onClick={() => setDismissed(true)}
aria-label="Dismiss notification"
className="absolute right-3 top-3 rounded-lg p-1.5 text-indigo-500/70 transition-colors hover:bg-indigo-100 hover:text-indigo-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-50 dark:text-indigo-300/70 dark:hover:bg-indigo-900/60 dark:hover:text-indigo-200 dark:focus-visible:ring-offset-indigo-950"
>
<svg
viewBox="0 0 20 20"
className="h-4 w-4"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
>
<path d="M5 5l10 10M15 5L5 15" />
</svg>
</button>
<div className="flex gap-4 pr-6">
<span
aria-hidden="true"
className="mt-0.5 flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-indigo-600 text-white shadow-sm dark:bg-indigo-500"
>
<svg
viewBox="0 0 24 24"
className="h-5 w-5"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M4 6h16v12H4z" />
<path d="M4 7l8 6 8-6" />
</svg>
</span>
<div className="min-w-0 flex-1">
{success ? (
<div className="aif-pop">
<h3 className="flex items-center gap-2 text-base font-semibold text-emerald-700 dark:text-emerald-300">
<svg
viewBox="0 0 24 24"
className="h-5 w-5"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M20 6L9 17l-5-5" />
</svg>
You're on the list
</h3>
<p className="mt-1 text-sm text-emerald-700/80 dark:text-emerald-300/80">
We'll email{" "}
<span className="font-medium">{email.trim()}</span> the
moment the new release ships. No spam, unsubscribe anytime.
</p>
</div>
) : (
<>
<h3 className="text-base font-semibold text-indigo-950 dark:text-indigo-50">
Get notified when we launch
</h3>
<p className="mt-1 text-sm text-indigo-900/70 dark:text-indigo-200/70">
Join 2,400+ builders getting early access to the new
dashboard. One email when it's live.
</p>
<form
onSubmit={handleSubmit}
noValidate
className="mt-4 flex flex-col gap-2 sm:flex-row"
>
<div className="min-w-0 flex-1">
<label htmlFor={fieldId} className="sr-only">
Email address
</label>
<input
ref={inputRef}
id={fieldId}
type="email"
inputMode="email"
autoComplete="email"
placeholder="you@company.com"
value={email}
onChange={handleChange}
aria-invalid={errorText}
aria-describedby={errorText ? msgId : undefined}
className={`w-full rounded-lg border bg-white px-3.5 py-2.5 text-sm text-neutral-900 shadow-sm transition-colors placeholder:text-neutral-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-50 dark:bg-neutral-900 dark:text-neutral-100 dark:placeholder:text-neutral-500 dark:focus-visible:ring-offset-indigo-950 ${
errorText
? "aif-shake border-rose-400 focus-visible:ring-rose-500 dark:border-rose-600"
: "border-indigo-200 focus-visible:ring-indigo-500 dark:border-indigo-800"
}`}
/>
</div>
<button
type="submit"
className="shrink-0 rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-indigo-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-indigo-50 active:bg-indigo-800 dark:bg-indigo-500 dark:hover:bg-indigo-400 dark:focus-visible:ring-offset-indigo-950"
>
Notify me
</button>
</form>
<p
id={msgId}
role={errorText ? "alert" : undefined}
className={`mt-2 text-xs ${
errorText
? "text-rose-600 dark:text-rose-400"
: "text-transparent"
}`}
>
{errorText
? "Please enter a valid email address."
: "placeholder"}
</p>
</>
)}
</div>
</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.

