# Inline Newsletter Form in HTML and CSS

A shadcn/ui block from the [@weinc](https://we.inc/components?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/weinc/inline-newsletter-form-html-css).

- Page, with a live preview: https://shadcn.exchange/r/weinc/inline-newsletter-form-html-css
- Screenshot: https://shadcn.exchange/shot/weinc/inline-newsletter-form-html-css/light.png
- Registry item: https://we.inc/r/inline-newsletter-form-html-css.json
- Listed: 2026-10-29, source last changed 2026-10-29

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/weinc/inline-newsletter-form-html-css.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Files

### `components/we-inc/inline-newsletter-form-html-css.tsx`

```tsx
import { useState, type FormEvent } from "react";

const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;

export default function InlineNewsletterForm() {
  const [email, setEmail] = useState("");
  const [state, setState] = useState<"idle" | "bad" | "done">("idle");

  const submit = (e: FormEvent) => {
    e.preventDefault();
    setState(EMAIL.test(email) ? "done" : "bad");
  };

  return (
    <section className="bg-[#10172a] px-4 py-14">
      <div className="mx-auto flex max-w-5xl flex-col gap-8 lg:flex-row lg:items-center lg:justify-between">
        <div className="max-w-md">
          <h2 className="text-2xl font-semibold tracking-tight text-white">The Field Notes letter</h2>
          <p className="mt-2 text-slate-300">One email on the first Sunday of each month: new trail guides, kit we have stopped using, and a map worth printing.</p>
        </div>

        <form noValidate onSubmit={submit} className="w-full lg:max-w-md">
          <label htmlFor="inlf-email" className="sr-only">Email address</label>
          <div className={`flex flex-col gap-2 rounded-2xl bg-white/5 p-1.5 ring-1 sm:flex-row sm:rounded-full ${state === "bad" ? "ring-rose-400" : "ring-white/15 focus-within:ring-2 focus-within:ring-lime-300"}`}>
            <input
              id="inlf-email"
              type="email"
              autoComplete="email"
              required
              value={email}
              onChange={(e) => { setEmail(e.target.value); if (state !== "idle") setState("idle"); }}
              placeholder="you@example.com"
              aria-invalid={state === "bad" || undefined}
              aria-describedby="inlf-msg"
              className="h-11 min-w-0 flex-1 rounded-full bg-transparent px-4 text-[15px] text-white placeholder:text-slate-400 focus:outline-none"
            />
            <button type="submit" className="h-11 shrink-0 rounded-full bg-lime-300 px-6 text-sm font-semibold text-slate-900 hover:bg-lime-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-[#10172a]">
              {state === "done" ? "Subscribed" : "Subscribe"}
            </button>
          </div>
          <p id="inlf-msg" role="status" className={`mt-2.5 px-4 text-sm ${state === "bad" ? "text-rose-300" : state === "done" ? "text-lime-200" : "text-slate-400"}`}>
            {state === "bad" ? "Check the address, it needs an @ and a domain." : state === "done" ? "Done. The next letter goes out on Sunday." : "No tracking pixels. Leave with one click."}
          </p>
        </form>
      </div>
    </section>
  );
}
```
