Web InnoventixFreeCode

Inline Form Alert

Original · free

An alert containing an inline email input and button.

byWeb InnoventixReact + Tailwind
alertinlineformalerts
Open

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
alert-inline-form.tsx
"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&apos;re on the list
                  </h3>
                  <p className="mt-1 text-sm text-emerald-700/80 dark:text-emerald-300/80">
                    We&apos;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&apos;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

None (React + Tailwind only).

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 quote

Similar components

Browse all →