Call to Action

Start building with Spruce today

Get the newsletter with new components, styles and tips. One email a month.

Join 12,000+ developers
@use 'sprucecss/scss/spruce' as *;

.cta-block {
  padding-block: spacer-clamp('xl', 'xxl');

  &__box {
    @include set-css-variable((
      --base-color-heading: color('primary-foreground', 'btn'),
      --base-color-text: color('primary-foreground', 'btn'),
    ));
    @include selection(color('secondary-background', 'btn'), color('secondary-foreground', 'btn'));
    align-items: center;
    background-color: color('primary-background', 'btn');
    background-image:
      radial-gradient(circle at 0% 0%, #{color-alpha('primary-foreground', 0.18, 'btn')}, transparent 45%),
      radial-gradient(circle at 100% 100%, #{color-alpha('secondary-background', 0.5, 'btn')}, transparent 50%);
    border-radius: radius('lg');
    box-shadow: shadow('xl');
    color: color('primary-foreground', 'btn');
    display: flex;
    flex-direction: column;
    gap: spacer('m');
    padding: spacer-clamp('l', 'xl') spacer-clamp('m', 'xl');
    text-align: center;
  }

  // The box is a flex column (it centers the form), so its text children drop the browser margins.
  &__title {
    font-size: responsive-font-size(2.75rem, 35, '3vw + 1rem');
    margin-block: 0;
    max-inline-size: 36rem;
  }

  &__text {
    margin-block: 0;
    max-inline-size: 34rem;
    opacity: 0.9;
  }

  &__form {
    display: flex;
    flex-wrap: wrap;
    gap: spacer('xs');
    inline-size: 100%;
    max-inline-size: 30rem;

    .form-control {
      flex: 1 1 14rem;
    }

    .btn {
      flex: 0 0 auto;
    }
  }

  // The GDPR consent under the form, in the text color of the brand background.
  &__consent {
    flex-basis: 100%;
    font-size: config('font-size-sm', $typography);
    justify-content: center;
    text-align: start;

    .form-check__label,
    a {
      color: color('primary-foreground', 'btn');
    }

    .form-check__control:focus-visible {
      outline-color: color('primary-foreground', 'btn');
    }
  }

  &__actions {
    display: flex;
    flex-wrap: wrap;
    gap: spacer('xs');
    justify-content: center;
  }

  // Inverted buttons on the brand background.
  &__primary {
    background-color: color('primary-foreground', 'btn');
    border-color: color('primary-foreground', 'btn');
    color: color('primary-background', 'btn');

    &:hover {
      background-color: color-alpha('primary-foreground', 0.9, 'btn');
      color: color('primary-background', 'btn');
    }
  }

  &__secondary {
    background-color: transparent;
    border-color: color-alpha('primary-foreground', 0.6, 'btn');
    color: color('primary-foreground', 'btn');

    &:hover {
      background-color: color-alpha('primary-foreground', 0.12, 'btn');
      border-color: color('primary-foreground', 'btn');
      color: color('primary-foreground', 'btn');
    }
  }

  &__proof {
    align-items: center;
    display: flex;
    font-size: config('font-size-sm', $typography);
    gap: spacer('xs');

    .avatar {
      box-shadow: 0 0 0 2px color('primary-background', 'btn');
    }
  }
}
<section class="cta-block" aria-labelledby="cta-block-title">
    <div class="container">
        <div class="cta-block__inner">
            <div class="cta-block__box">
                <h2 class="cta-block__title" id="cta-block-title">Start building with Spruce today</h2>
                <p class="cta-block__text">Get the newsletter with new components, styles and tips. One email a month.</p>
                <form class="cta-block__form" onsubmit="return false;">
                    <label class="sr-only" for="cta-email">Email address</label>
                    <input class="form-control form-control--lg" id="cta-email" type="email" placeholder="you@example.com" autocomplete="email" required>
                    <button class="btn btn--secondary btn--lg" type="submit">Get started</button>
                    <label class="form-check cta-block__consent" for="cta-consent">
                        <input class="form-check__control" id="cta-consent" name="consent" type="checkbox" required>
                        <span class="form-label form-check__label">I agree to receive emails and accept the <a href="#">privacy policy</a>. Unsubscribe anytime.</span>
                    </label>
                </form>
                <div class="cta-block__proof">
                    <ul class="avatar-group" role="list" aria-hidden="true">
                        <li><span class="avatar avatar--sm"><img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=96&h=96&q=70" alt="" width="32" height="32" loading="lazy"></span></li>
                        <li><span class="avatar avatar--sm"><img src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=96&h=96&q=70" alt="" width="32" height="32" loading="lazy"></span></li>
                        <li><span class="avatar avatar--sm"><img src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=96&h=96&q=70" alt="" width="32" height="32" loading="lazy"></span></li>
                    </ul>
                    <span>Join 12,000+ developers</span>
                </div>
            </div>
        </div>
    </div>
</section>

Call to Action

On this page

A closing call to action: a colored banner with an email sign-up and social proof.

Technical Details

  • It combines a sign-up form (like the Newsletter component) with the Avatar group; load the avatar styles too.
  • The banner uses the primary button colors and redefines the heading and text color tokens for its subtree, so it reads well in every style and in dark mode.
  • The email input has a visually hidden label; the avatars are decorative (aria-hidden), the text carries the meaning.
  • Override the copy with ctaBlock (title, text, placeholder, action, proof).
  • Set actions ([{ label, href, primary }]) to show buttons instead of the form, for a final "buy now" call; the primary one is inverted on the brand background.
  • The form has a required GDPR consent checkbox, unchecked by default (consent must be an active choice), with a link to the privacy policy. Keep the wording specific: what the person signs up for and how to unsubscribe. Store the consent with a timestamp on the server. Change its text with consent.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.