Overview
CallToAction is a CORE-OWNED pure-content section component in src/core/components/sections/CallToAction.astro. It renders a call-to-action block with heading, body text, action buttons, and an optional media panel. Four layout variants control how content, actions, and media are arranged.
CallToAction does not own surface concerns (background, vertical padding, border-radius). Callers compose it inside LayoutSection for full-width bands or SurfaceBox for constrained card-style surfaces.
All CTA buttons include built-in analytics tracking via the cta_click event taxonomy.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
heading | string | required | CTA heading text |
body | string | — | Supporting body text (max-width 64ch) |
headingLevel | 1–6 | 2 | Semantic heading level |
headingSize | xs–display | "3xl" | Visual heading size (decoupled from semantic level) |
layout | "stacked" | "split" | "split-actions-start" | "split-actions-end" | "stacked" | Layout variant (see Behavior) |
media | SectionMedia | — | Image with src and alt. Required for the split layout to activate. |
primaryAction | SectionAction | — | Primary CTA button |
secondaryAction | SectionAction | — | Secondary CTA button. Only rendered when primaryAction is also present. |
as | "section" | "div" | "div" | HTML element for the root wrapper |
align | SectionAlign | "left" | Text and content alignment |
class | string | — | Additional CSS classes |
All other HTML attributes (id, aria-*, data-*, etc.) are forwarded to the root element via attribute passthrough.
Behavior
Layout variants
The layout prop selects from four arrangement strategies:
stacked(default) — Content and actions stack vertically in a single column. Actions appear below the content withlgspacing.split— Content occupies the left column, media occupies the right. At48remand above the columns display side-by-side. Requiresmedia— without it, the component falls back tostacked.split-actions-start— Actions appear in the left column, content in the right. Does not use media.split-actions-end— Content in the left column, actions in the right (right-aligned). Does not use media.
Layout resolution
The component applies a guard: layout="split" only takes effect when media is also provided. The split-actions-start and split-actions-end variants activate regardless of media. Any unresolvable layout falls back to "stacked".
Actions
secondaryAction is only rendered when primaryAction is also present. Each action renders as a Button component. The action’s variant defaults to "primary" for the primary action and "secondary" for the secondary action, but can be overridden via the SectionAction.variant field.
Alignment
The align prop controls text alignment and flex alignment of the content column:
"left"(default) — left-aligned text and items"center"— centered text and items"right"— right-aligned text and items
Analytics tracking
All CTA buttons carry a data-track-cta attribute. A delegated click listener (registered once per page) captures clicks on these elements and dispatches a cta_click event via the shared trackEvent function with label (button text) and href (link target) in the payload.
CSS API
BEM root class: call-to-action
| Class | Element |
|---|---|
.call-to-action | Block root element |
.call-to-action__inner | Inner layout container |
.call-to-action__content | Content area (heading + body) |
.call-to-action__heading | Heading element |
.call-to-action__body | Body text paragraph |
.call-to-action__actions | Actions container (button row) |
.call-to-action__media | Media wrapper (split layout only) |
.call-to-action__media-img | Image element |
Modifier classes applied automatically:
| Modifier | Applied when |
|---|---|
.call-to-action--align-center | align="center" |
.call-to-action--align-right | align="right" |
.call-to-action__inner--split | layout="split" with media |
.call-to-action__inner--split-actions-start | layout="split-actions-start" |
.call-to-action__inner--split-actions-end | layout="split-actions-end" |
These classes are available as CSS API hooks for site-level overrides in src/site/styles/.
Usage
A CTA inside a full-width section band:
<LayoutSection background="primary" verticalPadding="2xl">
<CallToAction
heading="Ready to get started?"
body="Join thousands of teams already using the platform."
primaryAction={{ label: "Sign Up", href: "/signup" }}
align="center"
/>
</LayoutSection>
A CTA as a rounded card inside a constrained surface:
<LayoutSection verticalPadding="2xl">
<SurfaceBox background="contrast" class="rounded-md py-lg">
<CallToAction
heading="Ready to get started?"
body="Join thousands of teams already using the platform."
primaryAction={{ label: "Sign Up", href: "/signup" }}
secondaryAction={{ label: "Contact Sales", href: "/contact" }}
align="center"
/>
</SurfaceBox>
</LayoutSection>
A split CTA with media:
<LayoutSection background="contrast" verticalPadding="2xl">
<CallToAction
heading="See it in action"
body="Watch how teams use the platform to ship faster."
layout="split"
media={{ src: "/images/demo.jpg", alt: "Product demo" }}
primaryAction={{ label: "Watch Demo", href: "/demo" }}
/>
</LayoutSection>
Note: To customize CTA appearance at the site level, target the BEM classes above in site-owned stylesheets rather than modifying the core component.