Skip to content
goodtabs UI
BrowseBrowse components

Newsletter section

The whole newsletter band: heading, copy, a form slot and fine print.

npx shadcn@latest add https://ui.goodtabs.co/r/newsletter-section.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/newsletter-section

Newsletter

One email a week

Five tabs worth keeping open, picked by a person.

No spam. Leave with one click.

Heading, support copy and an email signup, centered.

Left aligned

Newsletter

New components, every Friday

A short note on what shipped, with the code and one thing worth reading.

About 600 designers and engineers read it. Leave any time.

Left aligned for a page column, with a custom button label.

Usage

import { NewsletterSection } from "@/components/newsletter-section"
import { EmailSignup } from "@/components/ui/email-signup"

<NewsletterSection title="One email a week" note="Leave with one click.">
  <EmailSignup align="center" />
</NewsletterSection>

Props

PropTypeDefaultDescription
eyebrowstringSmall uppercase line above the title.
titleReactNodeThe headline.
descriptionReactNodeWhat lands in the inbox and how often.
noteReactNodeFine print under the form.
align"center" | "left""center"Centered for a footer band.
size"sm" | "default""default"Spacing scale. sm fits a card.
childrenReactNodeThe form. Pass an EmailSignup or your own.

Source

newsletter-section/newsletter-section.tsx
import * as React from "react";
import { cn } from "@/lib/utils";

type NewsletterSectionProps = Omit<React.ComponentProps<"section">, "title"> & {
  /** Small uppercase line above the title. */
  eyebrow?: string;
  /** The headline. */
  title: React.ReactNode;
  /** One or two sentences on what lands in the inbox and how often. */
  description?: React.ReactNode;
  /** The fine print under the form, for example how to leave. */
  note?: React.ReactNode;
  /** Centered for a footer band, left for a page column. */
  align?: "center" | "left";
  /** Spacing scale. "sm" fits a card or narrow column. */
  size?: "sm" | "default";
  /** The form. Pass an EmailSignup, or your own. */
  children: React.ReactNode;
};

/**
 * The whole newsletter band: heading, support copy, your form and a line of fine print.
 * Server component. The form is a slot, so the field, validation and submit stay in EmailSignup.
 */
export function NewsletterSection({
  eyebrow,
  title,
  description,
  note,
  align = "center",
  size = "default",
  children,
  className,
  ...props
}: NewsletterSectionProps) {
  const centered = align === "center";
  const sm = size === "sm";
  return (
    <section className={cn(sm ? "py-8" : "py-16 lg:py-24", className)} {...props}>
      <div className="mx-auto w-full max-w-6xl px-4 md:px-6">
        <div
          className={cn(
            "flex flex-col gap-4",
            centered ? "mx-auto max-w-xl items-center text-center" : "max-w-xl items-start text-left",
          )}
        >
          {eyebrow && <p className="m-0 font-mono text-xs tracking-wide text-muted-foreground uppercase">{eyebrow}</p>}
          <h2 className={cn("m-0 font-medium tracking-tight text-balance", sm ? "text-xl" : "text-3xl sm:text-4xl")}>
            {title}
          </h2>
          {description && <p className="m-0 text-sm text-muted-foreground sm:text-base">{description}</p>}
          <div className="w-full pt-1">{children}</div>
          {note && <p className="m-0 text-xs text-muted-foreground">{note}</p>}
        </div>
      </div>
    </section>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/newsletter-section/prompt.md, or the Copy AI prompt button above.

When to use

  • Above a footer or at the end of an article
  • When the signup needs a heading and promise around it

When not to use

  • For a single inline field, use email-signup alone
  • For a full contact form, use contact-section

Agent notes

  • Server component. The form is the children slot; it reuses email-signup, so install that item too
  • Pass align="center" to EmailSignup when the section is centered
  • Because email-signup is a client component, any onSubmit handler you pass needs a client wrapper