# Newsletter Inline Success — React Contact Section

> A single-line signup whose submit button shrinks into a spinner, then a green check, while the field collapses around a confirmation message.

- Category: [Contact & Newsletter](https://pixelpapa.com/components/contact.md)
- Tags: newsletter, form, morph, success, layout
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/contact/newsletter-inline-success

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/newsletter-inline-success.json"
```

The file is written to `components/pixelpapa/newsletter-inline-success.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { Check, LoaderCircle, RotateCcw } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useRef, useState, type FormEvent } from "react";
import { cn } from "@/lib/utils";

type Status = "idle" | "loading" | "success";
const spring = { type: "spring", stiffness: 380, damping: 30 } as const;
const readers = [
  { initials: "AK", from: "var(--chart-1)", to: "var(--chart-3)" },
  { initials: "JM", from: "var(--chart-2)", to: "var(--chart-5)" },
  { initials: "SO", from: "var(--chart-4)", to: "var(--chart-1)" },
  { initials: "LT", from: "var(--chart-3)", to: "var(--chart-2)" },
];

export default function NewsletterInlineSuccess() {
  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<Status>("idle");
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
  useEffect(() => () => void (timer.current && clearTimeout(timer.current)), []);

  function onSubmit(e: FormEvent) {
    e.preventDefault();
    if (status !== "idle") return;
    setStatus("loading");
    timer.current = setTimeout(() => setStatus("success"), 1200);
  }

  function reset() {
    setStatus("idle");
    setEmail("");
  }

  return (
    <section className="relative w-full overflow-hidden bg-background px-6 py-28">
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 [background:radial-gradient(50%_60%_at_50%_100%,color-mix(in_oklch,var(--primary)_14%,transparent),transparent)]"
      />
      <div className="relative mx-auto flex max-w-2xl flex-col items-center text-center">
        <p className="rounded-full border border-border bg-card px-3 py-1 text-xs font-medium text-muted-foreground">
          Every second Tuesday · 6 minute read
        </p>
        <h2 className="mt-6 text-5xl font-semibold tracking-tight text-balance text-foreground sm:text-6xl">
          Notes on interfaces that <span className="font-serif font-normal text-primary italic">feel</span> fast.
        </h2>
        <p className="mt-5 max-w-md text-lg text-muted-foreground">
          One idea, one teardown and one tiny code sample. Written by the Framewell design engineering team.
        </p>

        <motion.form
          layout
          onSubmit={onSubmit}
          transition={spring}
          style={{ borderRadius: 999 }}
          className={cn(
            "mt-10 flex h-14 items-center border border-input bg-card p-1.5 shadow-xl shadow-primary/5 focus-within:ring-4 focus-within:ring-primary/15",
            status === "success" ? "w-auto pl-6" : "w-full max-w-md pl-5",
          )}
        >
          <AnimatePresence mode="popLayout" initial={false}>
            {status === "success" ? (
              <motion.p
                key="message"
                layout="position"
                role="status"
                initial={{ opacity: 0, x: -12 }}
                animate={{ opacity: 1, x: 0 }}
                transition={{ delay: 0.12 }}
                className="mr-4 text-sm font-medium whitespace-nowrap text-foreground"
              >
                You are in. Issue 48 lands Tuesday.
              </motion.p>
            ) : (
              <motion.div key="field" layout="position" exit={{ opacity: 0 }} transition={{ duration: 0.12 }} className="mr-2 min-w-0 flex-1">
                <label htmlFor="newsletter-inline-success-email" className="sr-only">
                  Email address
                </label>
                <input
                  id="newsletter-inline-success-email"
                  type="email"
                  required
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  placeholder="ada@lovelace.dev"
                  disabled={status === "loading"}
                  className="h-10 w-full bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-60"
                />
              </motion.div>
            )}
          </AnimatePresence>
          <motion.button
            layout
            type="submit"
            transition={spring}
            style={{ borderRadius: 999 }}
            aria-label={status === "idle" ? undefined : status === "loading" ? "Subscribing" : "Subscribed"}
            className={cn(
              "flex h-11 shrink-0 items-center justify-center overflow-hidden text-sm font-medium transition-colors duration-300",
              status === "idle" ? "bg-primary px-6 text-primary-foreground" : "w-11",
              status === "loading" && "bg-primary text-primary-foreground",
              status === "success" && "bg-emerald-500 text-white",
            )}
          >
            <AnimatePresence mode="wait" initial={false}>
              <motion.span
                key={status}
                layout="position"
                initial={{ opacity: 0, scale: 0.5 }}
                animate={{ opacity: 1, scale: 1 }}
                exit={{ opacity: 0, scale: 0.5 }}
                transition={{ duration: 0.14 }}
                className="flex items-center"
              >
                {status === "idle" && "Subscribe"}
                {status === "loading" && <LoaderCircle className="size-5 animate-spin" />}
                {status === "success" && <Check className="size-5" strokeWidth={3} />}
              </motion.span>
            </AnimatePresence>
          </motion.button>
        </motion.form>

        <div className="mt-6 flex h-8 items-center gap-3 text-sm text-muted-foreground">
          {status === "success" ? (
            <button type="button" onClick={reset} className="inline-flex items-center gap-1.5 hover:text-foreground">
              <RotateCcw className="size-3.5" /> Use a different address
            </button>
          ) : (
            <>
              <span className="flex -space-x-2">
                {readers.map((r) => (
                  <span
                    key={r.initials}
                    className="flex size-7 items-center justify-center rounded-full text-[10px] font-semibold text-white ring-2 ring-background"
                    style={{ background: `linear-gradient(135deg, ${r.from}, ${r.to})` }}
                  >
                    {r.initials}
                  </span>
                ))}
              </span>
              Read by 18,400 designers and engineers. Unsubscribe in one click.
            </>
          )}
        </div>
      </div>
    </section>
  );
}
```

## AI prompt

```text
Add the "Newsletter Inline Success" component from PixelPapa UI to this project.

What it is: A single-line signup whose submit button shrinks into a spinner, then a green check, while the field collapses around a confirmation message.
Category: Contact & Newsletter. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/newsletter-inline-success.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/newsletter-inline-success.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/newsletter-inline-success" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More contact & newsletter

- [Contact Booking Slots](https://pixelpapa.com/components/contact/contact-booking-slots.md) (Pro): A meeting scheduler card: pick a day from a strip, then a time slot that highlights with a shared-layout pill, switch 12/24-hour format and confirm into a summary.
- [Contact Cards Channels](https://pixelpapa.com/components/contact/contact-cards-channels.md): Three contact channel cards, each with its own live detail: a copy-to-clipboard email, a chat thread with a typing indicator and a voice line with moving level bars.
- [Contact Map Office](https://pixelpapa.com/components/contact/contact-map-office.md) (Pro): A dotted SVG world map with pulsing office pins linked by animated arcs; the office list and the pins select each other and show live local time.
- [Contact Split Form](https://pixelpapa.com/components/contact/contact-split-form.md) (Pro): Two-column contact section whose form has floating labels, inline validation that slides in on blur, a shake on invalid submit and a drawn-check success state.
- [Newsletter Card Perks](https://pixelpapa.com/components/contact/newsletter-card-perks.md): A signup card with a staggered perks list on one side and a fanned stack of past issues that spreads apart on hover on the other.
