CTA — .mv-cta

Editorial call-to-action: a band between two hairlines with the headline on the left and details on the right, an always-dark ink slab, or a newsletter panel with inline form and status feedback.

CategoryBlocks
TypeBlock (ready-made section) (.mv-cta)
Statusstable
Also installsbutton, dot-pattern, input
Keywordscta, call to action, banner, newsletter, waitlist, signup, landing, editorial

When to use

Avoid when

Install

node scripts/add.mjs cta --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["cta"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, components/cta/cta.css, components/button/button.css, core/canvas.js, core/element.js, core/motion.js, core/pointer.js, components/dot-pattern/dot-pattern.css, components/dot-pattern/dot-pattern.js, components/input/input.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div id="cta-demo" style="background:var(--mv-bg)">
  <!-- Band (default) -->
  <section class="mv-cta" aria-labelledby="cta-b-title">
    <div class="mv-cta-inner">
      <div class="mv-cta-panel">
        <p class="mv-cta-eyebrow">Ready when you are</p>
        <h2 class="mv-cta-title" id="cta-b-title">Close September <span class="mv-cta-muted">in Ledgerline.</span></h2>
        <p class="mv-cta-text">Connect your bank feeds this afternoon and run your next month-end on a single screen. Our team migrates your checklist for you.</p>
        <div class="mv-cta-actions">
          <a class="mv-button" data-size="lg" href="#cta-demo">Start a free trial</a>
          <a class="mv-button" data-size="lg" data-variant="outline" href="#cta-demo">Book a demo</a>
        </div>
        <p class="mv-cta-note">
          <span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>30 days free</span>
          <span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>No card required</span>
          <span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>Free migration</span>
        </p>
      </div>
    </div>
  </section>

  <!-- Ink -->
  <section class="mv-cta" data-variant="ink" aria-labelledby="cta-i-title">
    <div class="mv-cta-inner" style="padding-top:0">
      <div class="mv-cta-panel">
        <div class="mv-cta-bg" aria-hidden="true">
          <div class="mv-dot-pattern" data-fill style="--mv-dot-pattern-mask: radial-gradient(ellipse 55% 90% at 100% 100%, #000 10%, transparent 70%)"></div>
        </div>
        <p class="mv-cta-eyebrow">For controllers &amp; CFOs</p>
        <h2 class="mv-cta-title" id="cta-i-title">Your auditor will <span class="mv-cta-muted">thank you.</span></h2>
        <p class="mv-cta-text">Give them a read-only link to every match, approval and journal of the year — signed, timestamped and exportable in one click.</p>
        <div class="mv-cta-actions">
          <a class="mv-button" data-size="lg" href="#cta-demo">Talk to a specialist</a>
          <a class="mv-button" data-size="lg" data-variant="ghost" href="#cta-demo">Security →</a>
        </div>
        <p class="mv-cta-note">SOC 2 Type II · ISO 27001 · GDPR, hosted in the EU</p>
      </div>
    </div>
  </section>

  <!-- Newsletter -->
  <section class="mv-cta" data-variant="newsletter" aria-labelledby="cta-n-title">
    <div class="mv-cta-inner" style="padding-top:0">
      <div class="mv-cta-panel">
        <p class="mv-cta-eyebrow">The Close Notes</p>
        <h2 class="mv-cta-title" id="cta-n-title">One month-end playbook, <span class="mv-cta-muted">every other Tuesday.</span></h2>
        <p class="mv-cta-text">Checklists, accrual templates and war stories from 21,000 finance leads. A four-minute read, never a sales pitch.</p>
        <form class="mv-cta-form" id="cta-news" novalidate>
          <label class="mv-sr-only" for="cta-email">Work email</label>
          <input class="mv-input" data-size="lg" id="cta-email" type="email" placeholder="[email protected]" autocomplete="email" aria-describedby="cta-news-status">
          <button class="mv-button" data-size="lg" type="submit">Subscribe</button>
        </form>
        <p class="mv-cta-note">Unsubscribe in one click. We never share your address.</p>
        <p class="mv-cta-status" id="cta-news-status" role="status" aria-live="polite"></p>
      </div>
    </div>
  </section>

  <script type="module">
    const form = document.querySelector("#cta-news");
    const status = document.querySelector("#cta-news-status");
    const input = form?.querySelector("input");
    const seen = new Set();
    let timer;
    const say = (state, text) => {
      clearTimeout(timer);
      status.dataset.state = state;
      status.textContent = text;
      timer = setTimeout(() => { status.textContent = ""; delete status.dataset.state; }, 5000);
    };
    form?.addEventListener("submit", (e) => {
      e.preventDefault();
      const email = input.value.trim().toLowerCase();
      if (!email) return;
      if (!input.checkValidity()) return say("error", "That address doesn’t look right. Check for a typo.");
      if (seen.has(email)) return say("error", "You’re already subscribed with this address.");
      seen.add(email);
      input.value = "";
      say("success", "Subscribed. The next issue lands on Tuesday.");
    });
  </script>
</div>

API

Attributes

NameTypeDefaultDescription
data-variantband | ink | newsletterbandband (default, or no attribute): open layout between two hairlines; ink: dark slab (color-scheme: dark) in both themes, the eyebrow sits on its own rule; newsletter: quiet bordered panel, form on the right. All switch to title | details columns from 52rem.

Content structure

NameDescription
.mv-cta-panelPanel: optional -bg (decorative layer), -eyebrow, -title (with -muted), -text, -actions or -form, -note, -status.
.mv-cta-formInline field + button; the button goes full width in containers under 26rem.
.mv-cta-statusForm feedback (role="status"), hidden when empty; data-state="success | error" colors it.
.mv-cta-noteReassurance line; use <span> elements with an icon for a list.

CSS classes

NameDescription
mv-ctaRoot section, mv-cta query container.

CSS variables

NameDefaultDescription
--mv-cta-panel-bgtransparent (ink: near-black, newsletter: var(--mv-bg-subtle))Panel background.
--mv-cta-rulevar(--mv-border-strong)Band hairlines.
--mv-cta-max72remMax width.
--mv-cta-bgtransparentSection background.

Accessibility

Section labeled by its title. The decorative layer is aria-hidden. The form has a visually hidden label, a native email field and a polite live status region linked with aria-describedby; errors are spelled out in text. The ink panel keeps its contrast in both themes.