A newsletter signup section in two columns on a warm cream background. The left column has the newsletter name, a headline, one sentence, an email field with a Subscribe button and a line about how often it is sent. The right column is a numbered list of four things a reader gets in every issue. Submitting the form swaps it for a confirmation message that repeats the address. On phones the form comes first and the list follows.
import { useState } from "react";
const benefits = [
{ title: "One idea you can try by Monday", text: "A single change to a product page, an email or a checkout, with the reasoning behind it." },
{ title: "A real shop, taken apart", text: "We walk through one independent store and show what it does well and what it could drop." },
{ title: "Numbers worth knowing", text: "Shipping rates, ad costs and platform fee changes, summed up in three lines." },
{ title: "Five minutes, no filler", text: "Short enough to read with a coffee. No sponsored posts dressed up as advice." },
];
export default function NewsletterSignupWithBenefitList() {
const [email, setEmail] = useState("");
const [done, setDone] = useState(false);
return (
<section className="bg-amber-50 px-4 py-16 text-stone-900 sm:px-6 sm:py-20">
<div className="mx-auto grid max-w-5xl gap-12 lg:grid-cols-2 lg:items-center lg:gap-16">
<div>
<p className="text-sm font-semibold uppercase tracking-wider text-orange-800">The Friday Counter</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight sm:text-4xl">A weekly letter for people who run small online shops</h2>
<p className="mt-4 text-lg leading-relaxed text-stone-700">Practical notes on selling more without spending more, written by two people who have packed their own orders.</p>
{done ? (
<p role="status" className="mt-8 flex items-start gap-3 rounded-md border border-orange-200 bg-white p-4 text-sm text-stone-800">
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" className="mt-0.5 h-5 w-5 flex-none text-orange-700">
<path d="M4.5 10.5l3.5 3.5 7.5-8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<span>
<span className="font-semibold">You are on the list.</span> Check {email} for a link to confirm your address.
</span>
</p>
) : (
<form
className="mt-8 flex flex-col gap-3 sm:flex-row"
onSubmit={(e) => {
e.preventDefault();
if (email.trim()) setDone(true);
}}
>
<label htmlFor="newsletter-email" className="sr-only">Email address</label>
<input
id="newsletter-email"
type="email"
required
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="[email protected]"
className="h-12 w-full rounded-md border border-stone-300 bg-white px-4 text-base text-stone-900 placeholder:text-stone-500 focus:border-orange-700 focus:outline-none focus:ring-2 focus:ring-orange-700/30"
/>
<button
type="submit"
className="h-12 flex-none rounded-md bg-orange-700 px-6 text-sm font-semibold text-white transition hover:bg-orange-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-orange-700 focus-visible:ring-offset-2 focus-visible:ring-offset-amber-50"
>
Subscribe
</button>
</form>
)}
<p className="mt-3 text-sm text-stone-700">Sent every Friday morning. Unsubscribe with one click.</p>
</div>
<div>
<h3 className="text-sm font-semibold uppercase tracking-wider text-stone-700">In every issue</h3>
<ol className="mt-4 divide-y divide-amber-200 border-y border-amber-200">
{benefits.map((b, i) => (
<li key={b.title} className="flex gap-5 py-5">
<span aria-hidden="true" className="w-7 flex-none text-2xl font-semibold tabular-nums text-orange-700">{i + 1}</span>
<div>
<p className="font-semibold">{b.title}</p>
<p className="mt-1 text-sm leading-relaxed text-stone-700">{b.text}</p>
</div>
</li>
))}
</ol>
</div>
</div>
</section>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/newsletter-signup-with-benefit-list.json
Only the email address. Each extra field lowers the number of people who finish. Ask for a name or interests later, in the welcome email or a preferences page.
An email address has a cost to the person giving it. A short, concrete list of what arrives each week answers "what do I get" at the point of decision, which a bare field cannot do.
Most providers give you a form endpoint or an API. In `onSubmit`, send the `email` value there with `fetch`, then call `setDone(true)` when the request succeeds and show an error message if it fails.
It is a good default. A confirmation link keeps mistyped and fake addresses off your list and is required or expected in several countries. The confirmation message here already tells the reader to check their inbox.
Yes. Copy the HTML version and point the form's `action` at your provider. You lose the inline confirmation, so send people to a thank-you page instead.
Related: Split Hero with Email Capture, Call-to-Action Banner on a Brand Colour, Sticky Bottom Offer Bar, Button Set: Primary, Secondary, Ghost, Icon and Loading.
More forms or all free UI components. Start for free to have a full site built around it.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.