Retour au catalogue

CTA Dark

CTA sur fond sombre avec contraste fort. Accent lumineux sur fond dark pour impact maximal.

ctasimple Dark Responsive a11y
darkboldagencysaascentered
Theme

How to build a dark CTA section with accent glow in React

A dark React CTA section places a centered text block over a deep background, uses a blurred radial div at 6% opacity to emit a soft accent glow, and animates the whole block into view on scroll with a Framer Motion whileInView transition. A highlighted word in the heading is colored via a CSS custom property to keep the accent consistent with the design system theme.

  • Stack: React + Framer Motion + Lucide React + Tailwind v4, ~110 lines, zero extra dependencies.
  • Animation: single whileInView block (opacity 0→1, y 24→0, duration 0.6s, custom ease [0.16, 1, 0.3, 1]).
  • Theming: fully token-driven, background, accent and border colors come from CSS custom properties, not hardcoded values.
  • Accessible: semantic <h2> + <a> tags, ARIA-hidden on the decorative glow div.
  • Responsive: fluid type scale via Tailwind breakpoints (text-3xl / md:text-4xl / lg:text-5xl), works on all screen sizes.

CtaDark is a centered call-to-action section built for dark-themed landing pages. It pairs a deep background with a subtle radial accent glow, visible enough to draw the eye, soft enough not to distract. A single Framer Motion entrance makes the entire block feel intentional rather than static. This is the section you drop at the end of a page when you need one clear, high-contrast action.

Anatomy

The section has three layers stacked inside a relative container. At the bottom sits the background color (--color-background-dark). The middle layer is an absolutely centered div, 600x400px, blurred at 120px, colored with --color-accent at 6% opacity. It is aria-hidden and pointer-events:none. On top, a max-w-2xl centered motion.div holds the heading, a paragraph, and a button row. The primary button is a filled pill with --color-accent background; an optional secondary button is an outlined pill with --color-border-dark.

How it works

The animation uses a single Framer Motion motion.div with whileInView, so the block only animates the first time it enters the viewport (viewport: { once: true }). Initial state is opacity:0 and y:24; the animated state is opacity:1 and y:0. The transition runs 0.6s with a custom cubic-bezier ease [0.16, 1, 0.3, 1], a strong ease-out that decelerates fast near the end, giving a punchy but smooth pop-up feel. The title accent word is computed with a simple string split on titleAccent, then wrapped in a span colored with var(--color-accent). No canvas, no SVG, no extra hooks required.

How to build it in React

  1. Set up the dark section shell

    Create a relative/overflow-hidden section with background-color:var(--color-background-dark) and generous vertical padding. This is the canvas everything else sits on. Using a CSS token instead of a hardcoded hex color means all seven library themes work out of the box.

    <section
      className="relative overflow-hidden"
      style={{
        backgroundColor: "var(--color-background-dark)",
        paddingTop: "var(--section-padding-y-lg)",
        paddingBottom: "var(--section-padding-y-lg)",
      }}
    >
  2. Add the radial accent glow

    Drop an absolutely centered div, 600x400px, blurred at 120px with blur-[120px], and set its backgroundColor to var(--color-accent) at opacity 0.06. Keep it aria-hidden and pointer-events-none so it is invisible to assistive technology and cannot intercept clicks.

    <div
      aria-hidden
      className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
                 w-[600px] h-[400px] rounded-full blur-[120px] pointer-events-none"
      style={{ backgroundColor: "var(--color-accent)", opacity: 0.06 }}
    />
  3. Animate the content with whileInView

    Wrap the heading, description and buttons in a single motion.div with initial={{ opacity:0, y:24 }} and whileInView={{ opacity:1, y:0 }}, viewport={{ once:true }}. Pass a 0.6s transition with a custom ease for a punchy scroll reveal that fires once and stays.

    <motion.div
      initial={{ opacity: 0, y: 24 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true }}
      transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
      className="text-center max-w-2xl mx-auto"
    >
  4. Highlight the accent word in the heading

    Split the title string on the titleAccent word, then render the parts with a span in between colored by var(--color-accent). This keeps the accent tied to the design system token, so switching themes updates the highlight automatically without touching the component.

    const titleParts = titleAccent ? title.split(titleAccent) : [title];
    
    // In JSX:
    {titleAccent ? (
      <>
        {titleParts[0]}
        <span style={{ color: "var(--color-accent)" }}>{titleAccent}</span>
        {titleParts[1]}
      </>
    ) : title}

When to use it

Use CtaDark at the bottom of a dark-themed SaaS or agency page, after testimonials or a feature section, when you want one high-contrast action before the footer. The composition guide in the meta explicitly calls for it in a 'late' position. Avoid it mid-page or directly after another CTA; the dark background needs breathing room to read as intentional. On light-themed pages, prefer a light variant with the same structure instead of fighting the contrast.

Used by

  • Stripe, Uses dark-background CTA sections at the bottom of product pages with a single high-contrast primary action.
  • Vercel, Dark CTA blocks with radial gradient glows appear at the end of feature and case-study pages.
  • Linear, Centered dark CTA panels with accent-colored headline highlights close out the main landing page.

FAQ

How do I change the accent glow color?

The glow color comes from var(--color-accent), so it follows whichever theme preset is active on the page. Switch the data-theme attribute on the parent element to one of the seven library presets and the glow updates automatically.

Can I add a second button?

Pass a value to the ctaSecondaryLabel prop and the outlined secondary button appears automatically next to the primary one. Both share the ctaUrl prop; if you need separate URLs, fork the component and add a ctaSecondaryUrl prop.

Does the scroll animation replay on every visit?

No. viewport: { once: true } in the whileInView config means the animation fires once per page load when the section enters the viewport, then stays in its animated state. Remove that flag if you want it to reset every time the section exits and re-enters.

What happens if titleAccent is not found in the title string?

The split returns an array with the full title as its only element, and the titleParts logic falls back to rendering the plain title without any highlighted span. No error is thrown; the heading just displays without accent coloring.

"use client";

import { motion } from "framer-motion";
import { ArrowRight } from "lucide-react";

interface CtaDarkProps {
  title?: string;
  titleAccent?: string;
  description?: string;
  ctaLabel?: string;
  ctaUrl?: string;
  ctaSecondaryLabel?: string;
}

export default function CtaDark({
  title = "Lancez votre projet",
  titleAccent = "projet",
  description = "Rejoignez les entreprises qui nous font confiance.",
  ctaLabel = "Commencer maintenant",
  ctaUrl = "#contact",
  ctaSecondaryLabel,
}: CtaDarkProps) {

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Reviews

React Dark CTA Section with Glow Effect, Code + Tutorial