Call to action blocks

Closing sections that ask the reader to sign up, start a trial or talk to sales. Each block is one .svelte file built from SvButton and SvToaster, themed from the --sg-* tokens. Each core owns its copy and actions and reports every press through a callback.

Installation

Add the whole group, or one block by its name (shown under each demo):

See them first with npx @svgrid/ui try blocks/cta. No project needed.

Centered

A heading, one line of copy and two buttons centered on an accent band. Start free trial shows its loading state, confirms with a toast and leaves a status line under the buttons; Talk to sales is a quiet link-style button. Where the browser supports relative colors, the band shifts the accent away from --sg-on-accent so the body copy stays readable on orange or light-blue accents. Below 560 px the buttons stack at full width.

Open the live example: Centered call to action block (Blocks)

npx @svgrid/ui add cta-centered

Justified

The heading and copy on the left, Book a demo and Get started on the right, in one bordered row. When Get started resolves, the buttons are replaced by a confirmation message. Below 760 px the row becomes a column and the buttons stack at full width with Get started first.

Open the live example: Justified call to action block (Blocks)

npx @svgrid/ui add cta-justified

Tracking clicks and linking to signup

Each action is { id, label, href }. The core takes two callbacks:

Leave onAction out and the core does not call preventDefault, so the button follows its href like a plain link. That is all a signup CTA needs:

const cta = createCtaCentered({
  primary: { id: 'trial', label: 'Start free trial', href: '/signup?plan=trial' },
  secondary: { id: 'sales', label: 'Talk to sales', href: '/contact/sales' },
  onTrack: (a) => {
    navigator.sendBeacon('/api/events', JSON.stringify({ name: 'cta_click', id: a.id, page: location.pathname }))
  },
})

sendBeacon is used because the browser leaves the page right after the click; a fetch started there can be cancelled. On the SvelteKit side, /signup is an ordinary route (src/routes/signup/+page.svelte) that reads url.searchParams.get('plan') in its load.

To create the account without leaving the page, pass onAction instead. In the justified block a resolved primary action sets finished, and the render shows doneText in place of the buttons:

const cta = createCtaJustified({
  onTrack: (a) => analytics.track('cta_click', { id: a.id }),
  onAction: async (a) => {
    if (a.id === 'demo') return goto('/demo')
    const r = await fetch('/api/trials', { method: 'POST' })
    if (!r.ok) throw new Error('Could not start the trial')
  },
  onError: (e) => toast.error(e instanceof Error ? e.message : 'Something went wrong'),
})

Both cores take heading, body and doneText too, so the copy can come from your CMS or a page's load data.

Headless

The state lives in the <script module> of each file: createCtaCentered() and createCtaJustified() own the copy, the actions, click tracking and the pending and done state. Take a core alone with npx @svgrid/ui add cta-justified --headless (it writes cta-justified.svelte.ts) and spread actionProps(action) on your own links. It carries the href, aria-busy, aria-disabled and the click handler:

<script lang="ts">
  import { createCtaJustified } from './cta-justified.svelte.js'

  const cta = createCtaJustified({
    onTrack: (a) => navigator.sendBeacon('/api/events', JSON.stringify({ id: a.id })),
  })
</script>

<section>
  <h2>{cta.heading}</h2>
  <p>{cta.body}</p>
  <a {...cta.actionProps(cta.secondary)}>{cta.secondary.label}</a>
  <a {...cta.actionProps(cta.primary)}>{cta.primary.label}</a>
</section>

See also

Live examples

  • Centered call to action block - A heading, one line and two SvButtons centered on an accent band. Start free trial shows its loading state, confirms in an SvToaster toast and leaves a status line; Talk to sales goes through the same tracked action.
  • Justified call to action block - A heading and one line on the left, Book a demo and Get started SvButtons on the right of one bordered row. Get started shows loading, confirms in a toast and replaces the buttons with a confirmation. Stacks on phones.

Related articles