Innovations

Newsletter Footer

Three link columns plus a wide newsletter signup form. Submit logs to console — wire to a real endpoint in use.

Preview

Source

tsx
"use client";

import { useState, FormEvent } from "react";
import { Button } from "@/components/ui/button";
import { Sparkles } from "lucide-react";

const columns = [
  { heading: "Product", links: ["Features", "Pricing", "Changelog", "Roadmap"] },
  { heading: "Company", links: ["About", "Blog", "Careers", "Press"] },
  { heading: "Resources", links: ["Docs", "Guides", "API reference", "Status"] },
];

export default function FooterNewsletter() {
  const [email, setEmail] = useState("");
  const [submitted, setSubmitted] = useState(false);

  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    // eslint-disable-next-line no-console
    console.log("Newsletter signup:", email);
    setSubmitted(true);
    setEmail("");
  };

  return (
    <footer className="w-full border-t border-border bg-background">
      <div className="container mx-auto px-6 py-12">
        <div className="grid grid-cols-2 md:grid-cols-5 gap-8">
          {columns.map((col) => (
            <div key={col.heading}>
              <h3 className="text-sm font-semibold text-foreground mb-4">{col.heading}</h3>
              <ul className="space-y-2">
                {col.links.map((l) => (
                  <li key={l}>
                    <a href="#" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
                      {l}
                    </a>
                  </li>
                ))}
              </ul>
            </div>
          ))}
          <div className="col-span-2">
            <h3 className="text-sm font-semibold text-foreground mb-4">
              Get the newsletter
            </h3>
            <p className="text-sm text-muted-foreground mb-3">
              Monthly product updates and essays. No spam.
            </p>
            <form onSubmit={onSubmit} className="flex gap-2">
              <input
                type="email"
                required
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="you@company.com"
                className="flex-1 min-w-0 rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50"
              />
              <Button type="submit" size="sm">Subscribe</Button>
            </form>
            {submitted && (
              <p className="text-xs text-primary mt-2">Thanks — check your inbox.</p>
            )}
          </div>
        </div>
        <div className="mt-10 pt-6 border-t border-border flex items-center justify-between gap-4">
          <a href="#" className="flex items-center gap-2 text-foreground font-bold">
            <Sparkles className="w-5 h-5 text-primary" />
            <span>Acme</span>
          </a>
          <p className="text-xs text-muted-foreground">
            © {new Date().getFullYear()} Acme, Inc.
          </p>
        </div>
      </div>
    </footer>
  );
}
Claude Code Instructions

CLI Install

npx innovations add newsletter

Where to use it

Use when the newsletter is a primary growth channel. In Astro (src/layouts/Layout.astro): import FooterNewsletter from '../components/innovations/footers/newsletter'; IMPORTANT: the onSubmit handler currently console.logs the email. Replace with a real API call (e.g., Resend, Beehiiv, ConvertKit, or a /api/subscribe endpoint) before launch. The submit button is a shadcn Button — add a loading state if your endpoint is slow. Customize: edit the columns array at the top. Keep to 3 columns so the newsletter column has room to breathe (col-span-2 on md+).