VibeUI
ComponentsDocs
VibeUI

Components with taste, that you own. Copy the source, retint the tokens, keep every line.

Library

  • All components
  • Signature pieces
  • Text effects
  • Footers

Docs

  • Introduction
  • Installation
  • Theming
  • CLI

Project

  • GitHub
  • Agents and MCP
  • Issues
  • MIT license
© 2026 VibeUI · MIT licensed
  1. Components
  2. Footers
  3. Footer Newsletter

Footer Newsletter

Email signup card with an inline success state.

FootersClient component

Stay in the loop

Release notes and new components. No spam.

Install

bash
npx @agents-npm-packages/vibeui add footer-newsletter --dir ./src/components/ui

Also installs button, input, which it imports.

Import

tsx
import { FooterNewsletter } from "@/components/ui/footer-newsletter"

API

Generated from the component's TypeScript types, so it always matches the source. * marks a required prop.

<FooterNewsletter>

PropTypeDefaultDescription
titlestring"Stay in the loop"Heading text.
descriptionstring"Release notes and new components. No spam."Supporting text under the title.
classNamestring—Extra classes, merged with the component's own.
onSubmit((email: string) => void)—Called with the email address on submit.

Source

src/components/ui/footer-newsletter.tsx
"use client";

import * as React from "react";
import { CheckCircle } from "@phosphor-icons/react";
import { cn } from "@/lib/utils";
import { Button } from "./button";
import { Input } from "./input";

/** Email signup card with an inline success state. */
export function FooterNewsletter({
  title = "Stay in the loop",
  description = "Release notes and new components. No spam.",
  className,
  onSubmit,
}: {
  title?: string;
  description?: string;
  className?: string;
  onSubmit?: (email: string) => void;
}) {
  const [email, setEmail] = React.useState("");
  const [done, setDone] = React.useState(false);

  return (
    <div
      className={cn(
        "rounded-lg border border-line bg-surface p-6 shadow-quiet",
        className,
      )}
    >
      <p className="text-[15px] font-medium tracking-[-0.01em] text-ink">{title}</p>
      <p className="mt-1 text-sm text-muted">{description}</p>
      {done ? (
        <p className="mt-4 flex animate-pop-in items-center gap-2 text-sm font-medium text-pastel-sage-ink">
          <CheckCircle size={18} weight="fill" /> You’re on the list.
        </p>
      ) : (
        <form
          className="mt-4 flex flex-col gap-2 sm:flex-row"
          onSubmit={(e) => {
            e.preventDefault();
            onSubmit?.(email);
            setDone(true);
          }}
        >
          <Input
            type="email"
            required
            aria-label="Email address"
            placeholder="you@studio.com"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            className="sm:flex-1"
          />
          <Button type="submit">Subscribe</Button>
        </form>
      )}
    </div>
  );
}

Exports FooterNewsletter. Peer dependencies: clsx, tailwind-merge, @phosphor-icons/react.

PreviousFooter MegaNextFooter Simple