# Newsletter Block Two

> A Newsletter Block Two component for displaying a newsletter section.

A shadcn/ui block from the [@devsloka](https://ui.devsloka.in/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/devsloka/newsletter-block-two).

- Page, with a live preview: https://shadcn.exchange/r/devsloka/newsletter-block-two
- Screenshot: https://shadcn.exchange/shot/devsloka/newsletter-block-two/light.png
- Registry item: https://ui.devsloka.in/r/newsletter-block-two.json
- Listed: 2026-10-28, source last changed 2026-10-28

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/devsloka/newsletter-block-two.json
```

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

## Dependencies

- npm packages: `lucide-react`
- Registry items: `button`, `input`, `label`

## Files

### `src/registry/blocks/newsletter/newsletter-block-two.tsx`

```tsx
"use client";

import { useId, useState, type ComponentProps, type FormEvent } from "react";
import { ArrowUpRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";

export type NewsletterIssue = {
  number: number;
  title: string;
  /** Display date, e.g. "2 Oct 2026". */
  date: string;
  href?: string;
};

export type NewsletterBlockTwoProps = Omit<ComponentProps<"section">, "onSubmit"> & {
  eyebrow?: string;
  title?: string;
  description?: string;
  /** Recent issues listed as proof of what subscribers get. */
  issues?: NewsletterIssue[];
  footnote?: string;
  submitLabel?: string;
  successMessage?: string;
  onSubmit?: (values: { email: string }) => void | Promise<void>;
};

const DEFAULT_ISSUES: NewsletterIssue[] = [
  { number: 48, title: "Why we deleted our onboarding checklist", date: "2 Oct 2026", href: "#" },
  { number: 47, title: "A year of shipping on Thursdays only", date: "18 Sep 2026", href: "#" },
  { number: 46, title: "The support inbox as a product roadmap", date: "4 Sep 2026", href: "#" },
];

const DEFAULTS = {
  eyebrow: "Issue 48 · Every other Thursday",
  title: "Field notes on building calm software.",
  description:
    "A short letter from the people making Northwind: what we shipped, what we got wrong, and one idea worth stealing. Read in about four minutes.",
  footnote: "2,900 readers. No tracking pixels, one click to unsubscribe.",
  submitLabel: "Subscribe",
  successMessage: "You're in. The next issue lands on Thursday.",
};

type Status = "idle" | "sending" | "success" | "error";

export function NewsletterBlockTwo({
  eyebrow = DEFAULTS.eyebrow,
  title = DEFAULTS.title,
  description = DEFAULTS.description,
  issues = DEFAULT_ISSUES,
  footnote = DEFAULTS.footnote,
  submitLabel = DEFAULTS.submitLabel,
  successMessage = DEFAULTS.successMessage,
  onSubmit,
  className,
  ...props
}: NewsletterBlockTwoProps) {
  const headingId = useId();
  const emailId = useId();
  const [status, setStatus] = useState<Status>("idle");

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const email = String(new FormData(event.currentTarget).get("email") ?? "");
    setStatus("sending");
    try {
      await onSubmit?.({ email });
      setStatus("success");
    } catch {
      setStatus("error");
    }
  };

  return (
    <section
      data-slot="newsletter-block-two"
      aria-labelledby={headingId}
      className={cn("w-full bg-background text-foreground", className)}
      {...props}
    >
      <div className="mx-auto grid max-w-6xl gap-12 px-6 py-20 md:grid-cols-12 md:gap-10 md:py-28">
        <div className="md:col-span-7">
          <p className="font-mono text-xs uppercase tracking-wider text-muted-foreground">
            {eyebrow}
          </p>
          <h2
            id={headingId}
            className="mt-6 text-3xl font-semibold tracking-[-0.03em] text-balance sm:text-4xl md:text-5xl"
          >
            {title}
          </h2>
          <p className="mt-5 max-w-lg text-base leading-relaxed text-muted-foreground">
            {description}
          </p>

          <form onSubmit={handleSubmit} className="mt-10 max-w-md">
            <Label htmlFor={emailId} className="font-mono text-xs uppercase tracking-wider">
              Email address
            </Label>
            <div className="mt-2 flex gap-2">
              <Input
                id={emailId}
                name="email"
                type="email"
                required
                autoComplete="email"
                placeholder="you@company.com"
                disabled={status === "sending" || status === "success"}
                className="rounded-none"
              />
              <Button
                type="submit"
                disabled={status === "sending" || status === "success"}
                className="rounded-none"
              >
                {status === "sending" ? "Subscribing…" : submitLabel}
              </Button>
            </div>
            <p
              aria-live="polite"
              className={cn(
                "mt-3 text-sm",
                status === "error" ? "text-destructive" : "text-muted-foreground"
              )}
            >
              {status === "success"
                ? successMessage
                : status === "error"
                  ? "That didn't go through. Please try again."
                  : footnote}
            </p>
          </form>
        </div>

        {issues.length > 0 && (
          <aside className="md:col-span-5 md:pt-12" aria-label="Recent issues">
            <p className="border-b pb-3 font-mono text-xs uppercase tracking-wider text-muted-foreground">
              Recent issues
            </p>
            <ol className="divide-y">
              {issues.map((issue) => (
                <li key={issue.number}>
                  <a
                    href={issue.href}
                    className="group flex items-baseline gap-4 py-4 outline-none focus-visible:ring-2 focus-visible:ring-ring"
                  >
                    <span className="font-mono text-xs tabular-nums text-muted-foreground">
                      No. {issue.number}
                    </span>
                    <span className="flex-1">
                      <span className="block font-medium underline-offset-4 group-hover:underline">
                        {issue.title}
                      </span>
                      <span className="mt-1 block font-mono text-xs text-muted-foreground">
                        {issue.date}
                      </span>
                    </span>
                    <ArrowUpRight
                      aria-hidden
                      className="size-4 shrink-0 text-muted-foreground transition-colors group-hover:text-foreground"
                    />
                  </a>
                </li>
              ))}
            </ol>
          </aside>
        )}
      </div>
    </section>
  );
}
```
