CTA Section
Closes a marketing page with one conversion prompt: eyebrow, headline, subtitle, and up to two buttons.
When to Use
Use when:
A marketing, landing, or solutions page ends and the reader should do something next. One per page, at the very bottom — it consolidates CTA markup that was previously hand-rolled on each page.
Variants
Full — eyebrow, subtitle, two buttons
The complete form. Two buttons work when the second is a genuinely lower-commitment path, not a rewording of the first.
<?= ws_cta_section('Plan your workshop today', [
'eyebrow' => 'Ready to start?',
'subtitle' => 'Build your agenda in 3 minutes with the drag-and-drop planner.',
'primaryBtn' => ['text' => 'Get Started', 'href' => '/planner/'],
'secondaryBtn' => ['text' => 'Browse the Library', 'href' => '/library/exercises/'],
'color' => 'learn',
]) ?>Minimal — title and one button
Often stronger. When the page has already made the argument, the CTA only needs to name the action.
<?= ws_cta_section('Discover your Facilitator DNA', [
'primaryBtn' => ['text' => 'Take the Quiz', 'href' => '/dna/'],
'color' => 'reflect',
]) ?>Colour
Five keys. Since Red Unification the four phase keys resolve to the same red, so the practical choice is phase versus neutral — neutral being the muted variant for pages that shouldn't shout.
<?= ws_cta_section('A quieter close', ['color' => 'neutral', /* … */]) ?>States
| State | Behavior |
|---|---|
| Default | Full-bleed panel with the content constrained to --container-max. Static — the section itself has no interactive state. |
| Button states | Hover, focus, and disabled all belong to Button, which both slots render through. |
| Omitted parts | Eyebrow, subtitle, and secondary button each render only when supplied. No empty space is reserved. |
| No buttons | Renders headline-only. Almost certainly a mistake — a CTA section without a call to action is just a heading. |
| Button defaults | Passing an empty array for a slot still renders a button, with the fallback text "Get Started" or "Learn More". Omit the key entirely to leave a slot out. |
Real-World Usage
The end of a solutions page, with the CTA gated on auth state — a signed-in reader gets taken to the product, a visitor gets asked to sign up.
<?= ws_cta_section('Run a workshop people actually remember', [
'eyebrow' => 'Start free',
'subtitle' => 'The library, the planner, and the AI coach — no card required.',
'primaryBtn' => $isLoggedIn
? ['text' => 'Open the Planner', 'href' => '/planner/']
: ['text' => 'Create your first workshop', 'href' => '/signup',
'icon' => 'arrow_forward', 'iconPosition' => 'right'],
'secondaryBtn' => ['text' => 'See how it works', 'href' => '/about/'],
]) ?>Options
| Option | Type | Default | Purpose |
|---|---|---|---|
$title | string | '' | Positional headline. Make it the outcome, not the product. |
eyebrow | string | null | Small label above the title |
subtitle | string | null | One supporting sentence |
primaryBtn | array | null | Forwarded to ws_button(). Text defaults to "Get Started" if the array is present but has no text. |
secondaryBtn | array | null | Same, defaulting to "Learn More" |
color | string | 'learn' | learn | plan | facilitate | reflect | neutral |
class | string | '' | Additional CSS classes on the section |
attrs | array | [] | Extra HTML attributes on the section element |
Accessibility
| Concern | Behavior |
|---|---|
| Semantics | Renders a <section>. It has no accessible name of its own — add attrs => ['aria-labelledby' => …] if the page has several landmarks and this one needs distinguishing. |
| Heading level | The title is styled display text rather than a heading element, so it doesn't join the page outline. Deliberate for a closing panel, but it means screen-reader users browsing by heading won't land here — the buttons are how they'll find it. |
| Keyboard | Only the buttons are focusable, and they inherit Button's native behaviour. As the last thing on the page, they're the final tab stops. |
| Link text | The buttons are usually links. Name the destination: "Create your first workshop" beats "Get Started", which is meaningless in a screen reader's link list. |
| Eyebrow | Read before the title in document order, so the two should make sense together — "Ready to start? Plan your workshop today". |
| Contrast | Each colour key pairs a tested background with --color-ink or its muted variants. Don't override one side alone. |
Tokens
| Token | Used for |
|---|---|
--phase-learn / --phase-plan / --phase-facilitate / --phase-reflect (and -light pairs) | Panel background per colour key |
--bg-muted / --bg-page-warm | Neutral key background |
--color-border | Panel edge |
--color-ink / --color-ink-secondary / --color-ink-muted | Title, subtitle, and eyebrow text |
--container-max | Inner content width |
--text-h2 / --text-ui / --text-meta | Title, subtitle, and eyebrow type scale |
--font-heading / --font-body / --font-bold | Fraunces title, Inter subtitle |
--space-2 through --space-16 | Internal rhythm and the panel's generous vertical padding |
CSS Classes
| Class | Purpose |
|---|---|
.ws-cta-section | Full-bleed section |
.ws-cta-section--learn / --plan / --facilitate / --reflect / --neutral | Colour keys |
.ws-cta-section__container | Max-width wrapper |
.ws-cta-section__content | Text column |
.ws-cta-section__eyebrow | Small label above the title |
.ws-cta-section__title | Display headline |
.ws-cta-section__subtitle | Supporting paragraph |
.ws-cta-section__actions | Button row |
Files
| File | Purpose |
|---|---|
includes/components/helpers.php | ws_cta_section() helper function |
includes/components/cta-section.php | Template — layout and the button calls |
includes/components/components.css | Styles (.ws-cta-section rules) |