Skip to content

CTA Banner

Full-bleed yellow slab with a giant uppercase headline and an inline email capture form (pending + success states), ruled pattern in the corner.

brutalist/section/cta-banner
Open ↗

Source

"use client";

import { useId, useState, type FormEvent } from "react";
import { ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils";

export interface CtaBannerProps {
  title?: string;
  description?: string;
  placeholder?: string;
  buttonLabel?: string;
  finePrint?: string;
  successMessage?: string;
  /** Called with the submitted email. Return a promise to show a pending state. */
  onSubmit?: (email: string) => void | Promise<void>;
  className?: string;
}

export function CtaBanner({
  title = "Your next release deserves an audience.",
  description = "Connect a repo, merge a PR, watch your changelog write itself. Free forever for one project.",
  placeholder = "you@company.com",
  buttonLabel = "Get started",
  finePrint = "No credit card. Cancel anytime. SOC 2 Type II.",
  successMessage = "Check your inbox — your workspace is ready.",
  onSubmit,
  className,
}: CtaBannerProps) {
  const inputId = useId();
  const statusId = useId();
  const [status, setStatus] = useState<"idle" | "pending" | "done">("idle");

  async function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const email = new FormData(e.currentTarget).get("email");
    if (typeof email !== "string") return;
    setStatus("pending");
    await onSubmit?.(email);
    setStatus("done");
  }

  return (
    <section className={cn("da-section da-stroke-b bg-da-bg text-da-fg", className)}>
      <div className="da-container">
        <div className="da-stroke relative overflow-hidden bg-da-primary p-8 text-da-primary-fg shadow-da-lg sm:p-12 lg:p-16">
          {/* Decorative ruled pattern in the corner */}
          <div
            aria-hidden
            className="absolute -top-10 -right-10 size-56 rotate-12 opacity-20 [background-image:repeating-linear-gradient(0deg,currentColor_0_3px,transparent_3px_18px)]"
          />
          <div className="relative grid gap-10 lg:grid-cols-[1.5fr_1fr] lg:items-end">
            <div>
              <h2 className="font-da-display text-[clamp(2.25rem,5vw,4rem)] leading-[0.92] tracking-da-display uppercase">{title}</h2>
              <p className="mt-5 max-w-xl text-lg font-medium">{description}</p>
            </div>
            <div>
              {status === "done" ? (
                <p role="status" className="da-stroke bg-da-surface p-5 font-bold text-da-surface-fg">
                  ✓ {successMessage}
                </p>
              ) : (
                <form onSubmit={handleSubmit} className="flex flex-col gap-3 sm:flex-row" aria-describedby={statusId}>
                  <label htmlFor={inputId} className="sr-only">
                    Work email
                  </label>
                  <input
                    id={inputId}
                    name="email"
                    type="email"
                    required
                    autoComplete="email"
                    placeholder={placeholder}
                    className="da-focus da-stroke min-w-0 sm:flex-1 bg-da-input px-4 py-3.5 text-base text-da-fg placeholder:text-da-muted-fg"
                  />
                  <button
                    type="submit"
                    disabled={status === "pending"}
                    className="da-focus da-transition da-stroke inline-flex items-center justify-center gap-2 bg-da-fg px-6 py-3.5 font-bold text-da-bg shadow-da-sm hover:translate-x-[3px] hover:translate-y-[3px] hover:shadow-none disabled:opacity-60"
                  >
                    {status === "pending" ? "Sending…" : buttonLabel}
                    <ArrowRight aria-hidden className="size-5" />
                  </button>
                </form>
              )}
              <p id={statusId} className="mt-3 font-da-mono text-xs font-bold tracking-da-label uppercase">
                {finePrint}
              </p>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

export default CtaBanner;

modules/brutalist/section/cta-banner/index.tsx

Props

PropTypeDefaultDescription
titlestring—Headline (h2).
descriptionstring—Supporting paragraph.
placeholderstring"you@company.com"Email input placeholder.
buttonLabelstring"Get started"Submit button label.
finePrintstring—Reassurance line under the form.
successMessagestring—Message shown after submit.
onSubmit(email: string) => void | Promise<void>—Submit handler; awaited to show the pending state.
classNamestring—Extra classes on the <section>.

Other cta variants in Brutalist

CTA in other art directions

Cta App

App download CTA in a glass panel: headline, ink store buttons and rating on the left; an HTML phone mock-up recording an in-person meeting (mic orb, waveform, AI chip) rising from the bottom on the right.

GlassCTA

Cta Gradient

Bold full-gradient CTA card with a masked grid overlay and top glow, white primary button, translucent secondary button and three frosted stat chips.

GlassCTA

CTA Orb

Large frosted panel with a slowly spinning, heavily blurred conic-gradient orb glowing through the glass, centered headline, ink + frosted CTAs and a reassurance line.

GlassCTA

Cta Waitlist

Frosted CTA panel haloed by a blurred gradient ring: headline, copy, pill email form with gradient button (success state) and three check perks.

GlassCTA

Cta Banner

Compact inverted ink banner on a single row: title and one-liner on the left, solid and outline actions on the right, with a faint grid fading in from the right edge.

MinimalCTA

CTA Glow

Closing call-to-action card: centered headline and copy, ink button with keyboard hint, ghost secondary link, an indigo glow rising from the bottom edge and a gradient hairline on top.

MinimalCTA

Cta Newsletter

Centered email capture card with an indigo hairline on top: headline, copy, email field and ink button, with client validation plus pending, success and error states.

MinimalCTA

Cta Split

Wide two-column CTA card: headline, copy and two buttons on the left; a tinted panel on the right with a numbered ‘first 10 minutes’ onboarding checklist and time estimates.

MinimalCTA

Cta Contact

Studio-style contact CTA: mono label, headline, the email address set huge as a link, and a row of contact channels (studio, press, careers) in mono under a hairline.

Mono CleanCTA

Cta Email

Underline-input signup: large headline, then an email field drawn as a single bottom rule with an inline “Get started →” button; confirmation replaces the form.

Mono CleanCTA

Cta Giant

Full-width CTA as a giant link: the whole headline is the link, an arrow slides in on hover and an ink underline wipes across; a mono note sits below.

Mono CleanCTA

Cta Split

Two-door CTA: the section is split into two equal halves (white | ink) with a mono label and a large title each; hovering a half inverts it and nudges the arrow.

Mono CleanCTA

Cta Band

Full-width primary-blue band with a faint grid pattern, headline + subline on the left and inverted white + outlined buttons on the right.

Neo CorporateCTA

Cta Demo

Book-a-demo CTA: benefits list on the left, bordered form card (work email, company, team size select) on the right with an inline success state.

Neo CorporateCTA

Cta Stats

Split CTA on a dark (fg-colored) panel: copy + buttons on the left, a 2×2 grid of large outcome stats divided by hairlines on the right.

Neo CorporateCTA

Cta Trust

Centered CTA inside a bordered panel: headline, two buttons, a line of compliance assurances with shield icons, and a strip of customer wordmarks below a hairline.

Neo CorporateCTA

Cta Blob

Closing CTA inside a large sage organic blob (SVG shape scaled to the content) with a clay pebble and two leaves around it; serif headline and cream pill buttons.

Organic SoftCTA

Cta Journal

Newsletter CTA styled like a paper journal: clay-tinted card with serif title, recent issue list with mono numbers, and a rounded email + button form with a warm thank-you state.

Organic SoftCTA

Cta Pledge

Interactive pledge CTA: pick the commitments your team is ready for (round check cards), a counter of companies who pledged, and a CTA that reflects how many you chose.

Organic SoftCTA

Cta Wave

Full-width sage band with soft wavy top and bottom edges (SVG), centered serif headline with an italic word, and two pill buttons; flows naturally between cream sections.

Organic SoftCTA

Cta App

App-download CTA on a peach block: headline and ink store buttons on the left, a flat white phone mock-up with a “Today” checklist (mint done items) rising from the bottom on the right.

Soft FlatCTA

Cta Block

Big periwinkle rounded block with pastel circles peeking from the corners, a centered white headline and two pill buttons (solid white and translucent).

Soft FlatCTA

Cta Illustrated

Split CTA in a white rounded card: headline, ink pill button and note on the left; a composition of pastel circle, pill, rotated square and a ✅ tile on the right.

Soft FlatCTA

Cta Newsletter

Newsletter CTA on a mint block: a flat envelope made of shapes with a notification badge, headline, pill email form and a cheerful success state.

Soft FlatCTA