Newsletter Form
atroui/newsletter-formFreeBlock
Email newsletter form with editable CONTENT; posts to your API.
Install it
npx shadcn@latest add https://atroui.com/r/newsletter-form.jsonSource
1"use client"23import { CheckCircle2, Loader2, Mail } from "lucide-react"4import { useState, type FormEvent } from "react"56/**7 * Edit CONTENT to change copy and API endpoint.8 * Wire CONTENT.endpoint to your own route (Host API).9 */10const CONTENT = {11 placeholder: "you@company.com",12 submitLabel: "Subscribe",13 successMessage: "You're on the list - thanks!",14 endpoint: "/api/newsletter",15}1617export type NewsletterFormProps = {18 className?: string19}2021export function NewsletterForm({ className }: NewsletterFormProps) {22 const [email, setEmail] = useState("")23 const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">(24 "idle",25 )26 const [error, setError] = useState("")2728 const onSubmit = async (e: FormEvent) => {29 e.preventDefault()30 setStatus("loading")31 setError("")32 try {33 const res = await fetch(CONTENT.endpoint, {34 method: "POST",35 headers: { "Content-Type": "application/json" },36 body: JSON.stringify({ email }),37 })38 const data = (await res.json().catch(() => ({}))) as { error?: string }39 if (!res.ok) throw new Error(data.error ?? "Failed to subscribe")40 setStatus("success")41 setEmail("")42 } catch (err) {43 setStatus("error")44 setError(err instanceof Error ? err.message : "Something went wrong")45 }46 }4748 if (status === "success") {49 return (50 <div51 className={`flex items-center gap-2 text-sm text-emerald-600 dark:text-emerald-400 ${className ?? ""}`}52 >53 <CheckCircle2 className="size-4" aria-hidden />54 <span>{CONTENT.successMessage}</span>55 </div>56 )57 }5859 return (60 <form61 onSubmit={onSubmit}62 className={`flex flex-col gap-2 sm:flex-row ${className ?? ""}`}63 >64 <div className="relative min-w-0 flex-1">65 <Mail66 className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground"67 aria-hidden68 />69 <input70 type="email"71 required72 value={email}73 onChange={(e) => setEmail(e.target.value)}74 placeholder={CONTENT.placeholder}75 disabled={status === "loading"}76 className="w-full border border-border-subtle bg-background py-2.5 pr-4 pl-9 text-sm text-foreground outline-none focus:border-foreground disabled:opacity-60"77 aria-label="Email for newsletter"78 />79 </div>80 <button81 type="submit"82// … truncated
What it pulls in
npm packages
Files it writes
More from atroui
All 82 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Analytics Provideranalytics-provider | atroui | Blocks | Free | 1 dep | |
| AR Portfolioar-portfolio | atroui | Blocks | Free | 1 dep | |
| Before / Afterbefore-after-slider | atroui | Blocks | Free | no deps | |
| Calendly Embedcalendly-embed | atroui | Blocks | Free | 1 dep | |
| Changelogchangelog | atroui | Blocks | Free | no deps | |
| Command Menucommand-menu | atroui | Blocks | Free | 4 deps | |
| Contact Formcontact-form | atroui | Blocks | Free | 2 deps | |
| Contextual CTAcontextual-cta | atroui | Blocks | Free | 2 deps |
