Newsletter — Split with Preview
blockmarketingSignup section pairing the pitch and form with a preview of the last issue — subject line, date, and opening paragraph — so the reader sees what they are subscribing to.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/newsletter-split-preview.json$npx shadcn@latest add https://uipkge.dev/r/react/newsletter-split-preview.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/newsletter-split-preview.json$bunx shadcn@latest add https://uipkge.dev/r/react/newsletter-split-preview.jsonNamed registry:
npx shadcn@latest add @uipkge-react/newsletter-split-previewInstalls to:components/blocks/Variants
Loading interactive previews…
Loading interactive previews…
Files installed (1)
components/blocks/NewsletterSplitPreview.tsx3.8 kB
'use client' import { useState, type FormEvent } from 'react' import { Check, Mail } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Separator } from '@/components/ui/separator' const lastIssue = { subject: 'Why we version metric definitions instead of dashboards', date: 'Feb 18, 2026', readTime: '9 min', opening: 'Dashboards are the output. The definition is the thing teams argue about, so that is what belongs under review — with a diff, an owner, and a revert path. This issue walks through what changed when we moved ours into the repo.', } export function NewsletterSplitPreview() { const [email, setEmail] = useState('') const [submitted, setSubmitted] = useState(false) function subscribe(event: FormEvent) { event.preventDefault() if (!email.trim()) return setSubmitted(true) } return ( <section data-slot="newsletter-split-preview" className="bg-background"> <div className="mx-auto max-w-6xl px-6 py-20 lg:py-28"> <div className="grid items-center gap-10 lg:grid-cols-2 lg:gap-16"> <div> <Badge variant="secondary" className="gap-1.5"> <Mail className="size-3" aria-hidden="true" /> Monthly </Badge> <h2 className="mt-4 text-3xl font-semibold tracking-tight text-balance sm:text-4xl"> Notes from the people who build it </h2> <p className="text-muted-foreground mt-3 text-lg"> One email a month on metric modelling, access control, and warehouse cost. No product announcements. </p> <form className="mt-6 max-w-md" onSubmit={subscribe}> <div className="flex gap-2"> <Input value={email} onChange={(event) => setEmail(event.target.value)} type="email" required placeholder="you@company.com" autoComplete="email" aria-label="Email address" /> <Button type="submit" className="shrink-0"> Subscribe </Button> </div> <p className="mt-2 min-h-5 text-xs" aria-live="polite"> {submitted ? ( <span className="text-success inline-flex items-center gap-1.5"> <Check className="size-3" aria-hidden="true" /> Check your inbox to confirm. </span> ) : ( <span className="text-muted-foreground">8,400 subscribers · unsubscribe in one click.</span> )} </p> </form> </div> {/* Showing the last issue is the point: nobody should have to subscribe to find out what they are subscribing to. */} <Card> <CardContent className="p-6"> <div className="flex flex-wrap items-center gap-3"> <Badge variant="outline">Last issue</Badge> <span className="text-muted-foreground text-xs"> {lastIssue.date} · {lastIssue.readTime} </span> </div> <h3 className="mt-4 text-lg leading-snug font-semibold tracking-tight text-balance"> {lastIssue.subject} </h3> <Separator className="my-4" /> <p className="text-muted-foreground text-sm leading-relaxed">{lastIssue.opening}</p> <Button variant="link" className="mt-4 h-auto p-0 text-sm"> Read the full issue </Button> </CardContent> </Card> </div> </div> </section> ) }
Raw manifest:https://uipkge.dev/r/react/newsletter-split-preview.json