Skip to content
ilinxa/pro-ui

Newsletter Signup

alphav0.2.0

Newsletter signup card — inline email form or CTA-only variant, async status tracking, three tones, full i18n.

Category: MarketingUpdated: 2026-08-11Created: 2026-05-02Author: ilinxa

Context

First component in the marketing category. Drop-in CTA for sidebars / footers / heroes asking visitors to subscribe to a newsletter. Two source variants captured: inline-form (input + button, magazine-grid sidebar) and cta-only (button-only, news-detail-page sidebar). Form state is controlled-or-uncontrolled (mirrors React input convention); status is controlled-or-derived from a Promise-returning onSubmit. Migration origin: kasder kas-social-front-v0 NewsMagazineGrid.tsx + (platform)/news/[id]/page.tsx sidebar blocks. Composed by `magazine-layout` and `detail-page-news-01` in the news-domain family.

Installation

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Register the @ilinxa namespace (once per project)Add to your components.json. Merge with existing config.
"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}
Install the component
pnpm dlx shadcn@latest add @ilinxa/newsletter-signup

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/newsletter-signup-fixtures

Preview

Join our newsletter

Latest updates, straight to your inbox.

Demo source

demo.tsxtsx

Usage

When to use

Reach for NewsletterSignup for an email-capture CTA in a sidebar, footer, or hero — anywhere you want a brand-tinted block that asks visitors to subscribe. Two variants: inline-form (input + button) or cta-only (button-only, click leads elsewhere).

Minimal example

import { NewsletterSignup } from "@/components/newsletter-signup";

<NewsletterSignup
  onSubmit={async (email) => {
    await api.subscribe(email);
  }}
/>;

Returning a Promise from onSubmit auto-tracks the status: idle → pending → success on resolve, or idle → pending → error on reject. The card disables the input + button during pending and shows the appropriate status message.

CTA-only variant

<NewsletterSignup
  variant="cta-only"
  labels={{ button: "Sign up" }}
  onSubmit={() => openSignupModal()}
/>;

No email is captured here — the click handler typically opens a modal, navigates to a sign-up page, or triggers another flow.

Localization

<NewsletterSignup
  labels={{
    title: "Bültenimize Katılın",
    body: "En güncel haberleri e-posta ile alın.",
    placeholder: "E-posta adresiniz",
    button: "Abone Ol",
    successMessage: "Teşekkürler! Aboneliğiniz tamamlandı.",
    errorMessage: "Bir şeyler ters gitti. Lütfen tekrar deneyin.",
  }}
  onSubmit={subscribeAction}
/>;

Three tones

  • primary (default) — lime tint frame, signals the most editorial / brand-forward CTA.
  • accent — accent-tone frame, useful when primary is already used elsewhere on the page.
  • muted — neutral frame, good for low-noise placements like docs footers.

Controlled email value

const [email, setEmail] = useState("");

<NewsletterSignup
  value={email}
  onChange={setEmail}
  onSubmit={async (currentEmail) => {
    await api.subscribe(currentEmail);
  }}
/>;

Controlled status

If your form needs status from outside (e.g. a global success banner elsewhere on the page), drive the status prop:

<NewsletterSignup
  status={mutation.status === "loading" ? "pending" : mutation.status === "success" ? "success" : mutation.status === "error" ? "error" : "idle"}
  onSubmit={(email) => mutation.mutate(email)}
/>;

Accessibility

  • The form wraps input + button so Enter submits.
  • Input has aria-label from labels.emailLabel; default is Email address.
  • Status region uses aria-live="polite" for success and role="alert" for error.
  • Button gets aria-busy="true" during pending.
  • Heading semantic level via headingAs="h2" / "h3" / "h4".

Features

  • 2 visual variants — inline-form (input + button) and cta-only (full-width button only)
  • 3 tones — primary (lime tint default) / accent / muted, via `tone` prop
  • Controlled-or-uncontrolled email value (value + onChange OR defaultValue)
  • Controlled-or-derived status — pass `status` to drive externally OR return a Promise from `onSubmit` for auto-tracking idle → pending → success/error
  • Localizable — `labels` prop covers title / body / placeholder / button / success / error messages with English defaults
  • Form wrapping for Enter-to-submit; button disabled + aria-busy during pending; input disabled during pending
  • Status region uses aria-live=polite (success) and role=alert (error)
  • Heading semantic level configurable via `headingAs` (h2 | h3 | h4)
  • React.memo wrapped — prevents re-renders when used in long feeds

Tags

newsletter-signupmarketingctaformsubscribemigration

Dependencies

shadcn primitives: button, input