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.
| Category | Blocks |
|---|---|
| Type | Block (ready-made section) (.mv-cta) |
| Status | stable |
| Also installs | button, dot-pattern, input |
| Keywords | cta, call to action, banner, newsletter, waitlist, signup, landing, editorial |
When to use
- A landing page needs a restrained closing call to action with headline and details side by side
- A dark ink slab should break the page rhythm before the footer without animated effects
- A newsletter or waitlist signup needs an inline form with success or error status
Avoid when
- The call to action is the first thing visitors see at the top of the page → use Hero instead
Install
node scripts/add.mjs cta --out ./src/marvelousAI 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 & 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
| Name | Type | Default | Description |
|---|---|---|---|
data-variant | band | ink | newsletter | band | band (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
| Name | Description |
|---|---|
.mv-cta-panel | Panel: optional -bg (decorative layer), -eyebrow, -title (with -muted), -text, -actions or -form, -note, -status. |
.mv-cta-form | Inline field + button; the button goes full width in containers under 26rem. |
.mv-cta-status | Form feedback (role="status"), hidden when empty; data-state="success | error" colors it. |
.mv-cta-note | Reassurance line; use <span> elements with an icon for a list. |
CSS classes
| Name | Description |
|---|---|
mv-cta | Root section, mv-cta query container. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-cta-panel-bg | transparent (ink: near-black, newsletter: var(--mv-bg-subtle)) | Panel background. |
--mv-cta-rule | var(--mv-border-strong) | Band hairlines. |
--mv-cta-max | 72rem | Max width. |
--mv-cta-bg | transparent | Section 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.