Skip to content
All Taste components

CTA

Newsletter signup

A short pitch for a newsletter with an email field and a plain promise about what people will get.

copy + inline form · labelled field · privacy note · stacks on small screens

The Friday ledger

One short email a week on pricing, contracts and getting paid as a freelancer.

No spam. Unsubscribe in one click.

"use client";
import { useState } from "react";
import { Mail } from "lucide-react";

export function NewsletterSignup({ onSubscribe }: { onSubscribe: (email: string) => Promise<void> }) {
  const [email, setEmail] = useState("");
  const [state, setState] = useState<"idle" | "sending" | "done" | "error">("idle");

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    setState("sending");
    try { await onSubscribe(email); setState("done"); } catch { setState("error"); }
  }

  return (
    <section aria-labelledby="newsletter-title" className="bg-bg px-4 py-16 font-sans sm:px-6">
      <div className="mx-auto grid max-w-6xl items-center gap-8 rounded-xl border border-border bg-surface p-6 sm:p-10 lg:grid-cols-2">
        <div>
          <span className="inline-grid size-9 place-items-center rounded bg-primary-soft text-primary-soft-fg"><Mail className="size-[18px]" aria-hidden /></span>
          <h2 id="newsletter-title" className="mt-4 text-2xl font-semibold tracking-tight text-fg">The Friday ledger</h2>
          <p className="mt-2 text-sm leading-relaxed text-fg-muted">One short email a week on pricing, contracts and getting paid as a freelancer.</p>
        </div>
        <form onSubmit={submit} className="grid gap-2">
          <label htmlFor="newsletter-email" className="sr-only">Email address</label>
          <div className="flex flex-wrap gap-2">
            <input
              id="newsletter-email"
              type="email"
              required
              autoComplete="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="you@studio.com"
              className="h-10 min-w-0 flex-1 basis-48 rounded border border-border-strong bg-bg px-3 text-sm text-fg outline-none placeholder:text-fg-subtle focus:border-primary focus:ring-[3px] focus:ring-primary/20"
            />
            <button type="submit" disabled={state === "sending"} className="h-10 rounded bg-primary px-4 text-sm font-medium text-primary-fg transition hover:bg-primary-hover disabled:opacity-50">
              {state === "sending" ? "Subscribing…" : "Subscribe"}
            </button>
          </div>
          <p role="status" className="text-[13px] text-fg-subtle">
            {state === "done" ? "You’re in. Check your inbox to confirm." : state === "error" ? "That didn’t work. Try again in a moment." : "No spam. Unsubscribe in one click."}
          </p>
        </form>
      </div>
    </section>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.