Back to Widget Patterns
v2.2Lead Capture

Newsletter Signup Inline

Email signup with optional headline and supporting copy. Three style variants: default, minimal, and card. Shares form-state patterns with the inline single field form.

Live preview

Rendered with real component code. Each example demonstrates a documented variant.

Default

Unsubscribe anytime. No spam.

With headline

Get our weekly digest

One piece on product, growth, and distribution. Every Thursday.

Unsubscribe anytime. No spam.

Card style

Get our weekly digest

One piece on product, growth, and distribution. Every Thursday.

Unsubscribe anytime. No spam.

React props

Configure the component via props in React or via attributes in HTML.

PropTypeDefaultDescription
onSubmit(email: string) => void | Promise<void>requiredCalled with the trimmed email on submit; throw to trigger error state
headlinestringundefinedOptional heading displayed above the form row
descriptionstringundefinedOptional supporting copy displayed below the headline
placeholderstring"you@company.com"Input placeholder text
buttonLabelstring"Subscribe"Submit button label
disclaimerTextstring"Unsubscribe anytime. No spam."Fine print shown below the form
successTextstring"Thanks for subscribing."Message shown after a successful submit
style"default" | "minimal" | "card""default"Visual treatment of the container
classNamestringundefinedExtra class applied to the root element

HTML usage

The HTML variant uses the same shared styles and class names. Drop into any stack.

<link rel="stylesheet" href="path/to/newsletter-signup-inline/styles.css">

<!-- Default style -->
<section class="nsi nsi--default" id="my-newsletter">
  <h2 class="nsi__headline">Get the weekly essay</h2>
  <p class="nsi__description">One piece on B2B pricing, every Thursday.</p>

  <form class="nsi__form" novalidate>
    <label class="nsi__label nsi__label--visually-hidden" for="nsi-email">
      Email address
    </label>
    <input
      id="nsi-email"
      class="nsi__input"
      type="email"
      placeholder="you@company.com"
      autocomplete="email"
      required
    >
    <button class="nsi__button" type="submit">Subscribe</button>
  </form>

  <p class="nsi__disclaimer">Unsubscribe anytime. No spam.</p>
  <p class="nsi__success" hidden aria-live="polite">Thanks for subscribing.</p>
  <p class="nsi__error" hidden role="alert"></p>
</section>

<script>
(function () {
  var section = document.getElementById('my-newsletter');
  var form = section.querySelector('.nsi__form');
  var input = section.querySelector('.nsi__input');
  var button = section.querySelector('.nsi__button');
  var success = section.querySelector('.nsi__success');
  var error = section.querySelector('.nsi__error');

  form.addEventListener('submit', function (e) {
    e.preventDefault();
    var email = input.value.trim();
    if (!email) return;

    input.disabled = true;
    button.disabled = true;
    error.hidden = true;

    // Place your fetch() call here.
    fetch('/api/subscribe', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email: email }),
    })
      .then(function (res) {
        if (!res.ok) throw new Error('Request failed');
        success.hidden = false;
        form.reset();
      })
      .catch(function () {
        error.textContent = 'Something went wrong. Check your entry and try again.';
        error.hidden = false;
        input.disabled = false;
        button.disabled = false;
      });
  });
}());
</script>

Customization

Override these CSS custom properties to integrate the component into your brand.

.nsi {
  --nsi-bg: transparent;
  --nsi-padding: 0;
  --nsi-border: none;
  --nsi-border-radius: 0.5rem;
  --nsi-gap: 0.75rem;
  --nsi-headline-size: 1.25rem;
  --nsi-headline-weight: 700;
  --nsi-headline-color: inherit;
  --nsi-description-size: 0.9375rem;
  --nsi-description-color: var(--brand-muted, #6b7280);
  --nsi-input-border: var(--brand-border, #d1d5db);
  --nsi-input-border-radius: 0.375rem;
  --nsi-input-bg: #ffffff;
  --nsi-input-padding-y: 0.625rem;
  --nsi-input-padding-x: 0.875rem;
  --nsi-input-font-size: 1rem;
  --nsi-focus-ring: var(--brand-accent, #1e5fcf);
  --nsi-button-bg: var(--brand-accent, #1e5fcf);
  --nsi-button-bg-hover: color-mix(in srgb, var(--nsi-button-bg) 88%, black);
  --nsi-button-text: #ffffff;
  --nsi-button-font-weight: 600;
  --nsi-disclaimer-size: 0.8125rem;
  --nsi-disclaimer-color: var(--brand-muted, #6b7280);
  --nsi-success-color: var(--brand-success, #16a34a);
  --nsi-error-color: var(--brand-error, #dc2626);
}