Skip to content
Template Empire
Browse components

Newsletter Band

A free form input section for newsletter signups, with a one-time reveal and four honest submission states.

Design notes, once a month

Motion breakdowns and section teardowns from the templates we ship. No cadence padding, no reheated newsletters.

Newsletter Band is a free form input section for Next.js and React, styled with Tailwind and animated with Motion: a newsletter signup component react projects drop into a page as a finished band rather than a bare input. The heading, subtitle and form reveal once as it scrolls into view, and the idle, loading, success and error states swap inside a reserved-height grid cell, so nothing on the page moves at any point. Your code owns the request and passes the resulting state back in; the form itself is real, so native validation and Enter submission work with no JavaScript of ours.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/input-newsletter-band

Usage

"use client";

import { useState } from "react";
import {
  InputNewsletterBand,
  type NewsletterBandState,
} from "@/components/input-newsletter-band";

export default function Page() {
  const [state, setState] = useState<NewsletterBandState>("idle");

  return (
    <InputNewsletterBand
      title="Design notes, once a month"
      subtitle="Motion breakdowns from the templates we ship."
      state={state}
      onSubmit={async (email) => {
        setState("loading");
        try {
          const response = await fetch("/api/subscribe", {
            method: "POST",
            body: JSON.stringify({ email }),
          });
          setState(response.ok ? "success" : "error");
        } catch {
          setState("error");
        }
      }}
    />
  );
}

Props

PropTypeDefaultDescription
titlestringrequiredSection heading, rendered as an h2. Blocks never own the h1.
subtitlestringundefinedOptional supporting line beneath the heading.
state"idle" | "loading" | "success" | "error"requiredControlled submission state. The component performs no request of its own.
onSubmit(email: string) => voidrequiredCalled with the email on valid native submission, in the idle and error states only.
emailLabelstring"Email address"Visible label for the email field. The placeholder never replaces it.
emailPlaceholderstring"you@example.com"Supplementary placeholder, never the sole label.
submitLabelstring"Subscribe"Submit copy in the idle state.
loadingLabelstring"Subscribing"Shown beside the spinner and announced while loading.
successMessagestring"You're on the list."Success content and announcement.
errorMessagestring"Something went wrong. Please try again."Error content and announcement, shown as text as well as in the error colour.
namestring"email"Native email field name.
classNamestringundefinedAdditional classes merged onto the section.

Customisation

Newsletter Band exposes one Tier 3 CSS custom property. Set --input-newsletter-error on any ancestor to colour the error line. Its default var(--destructive, var(--foreground)) stays vanilla-safe by falling back to the foreground colour when a theme has no destructive token, and it is read with that default inline, so an override on :root or on a wrapper reaches the component. Every other choice is a real prop: the four state labels and messages, the field name, and the heading copy.

.marketing-footer {
  /* Any semantic colour: the default is var(--destructive, var(--foreground)).
     --ring exists in a stock shadcn theme, so this works on paste. */
  --input-newsletter-error: var(--ring);
}

Frequently asked questions

Is Newsletter Band free for commercial use?
Yes. Newsletter Band is MIT licensed, so you can use it in personal and commercial projects without attribution.
Is Newsletter Band accessible and performant?
Yes. It renders a real form with a visible label, native email validation and Enter submission, and a stable polite status region that announces the loading, success and error copy. Error is expressed in text as well as colour. The form and success panels share one grid cell so the taller state reserves the height and layout shift is zero, and whichever panel is inactive is inert and aria-hidden, so no invisible control stays in the Tab order. Focus transfers to the success message when it replaces the form, because the button it was on has just gone inert. Only transform and opacity animate, and reduced motion removes the travel, the spinner rotation and the check's scale while every behaviour stays intact.
How do I customise Newsletter Band?
Set --input-newsletter-error on any ancestor to colour the error line. Its default var(--destructive, var(--foreground)) stays vanilla-safe by falling back to the foreground colour when a theme has no destructive token, and it is read with that default inline, so an override on :root or on any wrapper reaches the component. Everything else is a real prop: title and subtitle for the copy, emailLabel, emailPlaceholder and submitLabel for the field and button, loadingLabel, successMessage and errorMessage for the four states, and name for the submitted field. Wiring it to your own endpoint is the state and onSubmit pair: onSubmit receives the validated email, and you set state to loading, then success or error, from your own request.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with conversion sections and resilient forms already composed. Browse the paid templates that pair with these free components.Explore Template Empire templates