Docs
CTA with Image

CTA with Image

A stunning split-layout CTA section with image and content side-by-side. Perfect for showcasing products or features with visual appeal.

Transform your workflow today

Join thousands of teams already using our platform to build better products faster.

  • Seamless integration with your existing tools
  • Real-time collaboration with your team
  • Advanced analytics and reporting
  • 24/7 customer support
Team collaboration

Ready to scale your business?

Everything you need to grow your business and reach new heights.

  • Unlimited projects and team members
  • Priority support and onboarding
  • Custom integrations and workflows
Business growth

Join our community

Connect with thousands of developers and designers building the future.

  • Access to exclusive resources
  • Weekly workshops and events
  • Network with industry leaders
Community

Features

  • Split layout - Image and content side-by-side
  • Theme variants - Default, Primary, and Dark themes
  • Image positioning - Left or right placement
  • Feature list - Optional checkmark list
  • Dual CTAs - Primary and optional secondary button
  • Responsive - Stacks on mobile, side-by-side on desktop
  • Next.js Image - Optimized image loading
  • Decorative elements - Subtle background accent
  • TypeScript support - Full type safety

Installation

Copy and paste the following code into your project.

"use client";
 
import { Button } from "@/components/ui/button";
import { ArrowRight, Check } from "lucide-react";
import Image from "next/image";
 
interface CTAImageSplitProps {
  headline: string;
  description?: string;
  features?: string[];
  imageUrl: string;
  imageAlt?: string;
  imagePosition?: "left" | "right";
  variant?: "default" | "primary" | "dark";
  primaryCTA: {
    text: string;
    href?: string;
    onClick?: () => void;
  };
  secondaryCTA?: {
    text: string;
    href?: string;
    onClick?: () => void;
  };
}
 
const variants = {
  default: {
    section: "bg-background",
    headline: "text-foreground",
    description: "text-muted-foreground",
    features: "text-foreground",
    primaryButton: "",
    secondaryButton: "",
  },
  primary: {
    section: "bg-primary text-primary-foreground",
    headline: "text-primary-foreground",
    description: "text-primary-foreground/80",
    features: "text-primary-foreground",
    primaryButton: "bg-background text-foreground hover:bg-background/90",
    secondaryButton:
      "border-primary-foreground/30 text-primary-foreground hover:bg-primary-foreground/10",
  },
  dark: {
    section: "bg-slate-950",
    headline: "text-white",
    description: "text-slate-400",
    features: "text-slate-300",
    primaryButton: "bg-white text-slate-950 hover:bg-slate-100",
    secondaryButton:
      "border-slate-700 text-white hover:bg-slate-900 hover:text-white",
  },
};
 
export function CTAImageSplit({
  headline,
  description,
  features,
  imageUrl,
  imageAlt = "CTA Image",
  imagePosition = "right",
  variant = "default",
  primaryCTA,
  secondaryCTA,
}: CTAImageSplitProps) {
  const styles = variants[variant];
 
  return (
    <section
      className={`relative overflow-hidden py-16 sm:py-24 ${styles.section}`}
    >
      <div className="container mx-auto px-6">
        <div
          className={`grid items-center gap-12 lg:grid-cols-2 lg:gap-16 ${
            imagePosition === "left" ? "lg:flex-row-reverse" : ""
          }`}
        >
          {/* Content */}
          <div
            className={`${imagePosition === "left" ? "lg:order-2" : "lg:order-1"}`}
          >
            <h2
              className={`mb-4 text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl ${styles.headline}`}
            >
              {headline}
            </h2>
 
            {description && (
              <p className={`mb-6 text-lg sm:text-xl ${styles.description}`}>
                {description}
              </p>
            )}
 
            {/* Features */}
            {features && features.length > 0 && (
              <ul className="mb-8 space-y-3">
                {features.map((feature, index) => (
                  <li key={index} className="flex items-start gap-3">
                    <div
                      className={`mt-1 flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full ${
                        variant === "default"
                          ? "bg-primary/10 text-primary"
                          : variant === "primary"
                            ? "bg-primary-foreground/20 text-primary-foreground"
                            : "bg-white/20 text-white"
                      }`}
                    >
                      <Check className="h-3.5 w-3.5 stroke-[3]" />
                    </div>
                    <span className={`text-base ${styles.features}`}>
                      {feature}
                    </span>
                  </li>
                ))}
              </ul>
            )}
 
            {/* CTAs */}
            <div className="flex flex-col gap-4 sm:flex-row">
              <Button
                size="lg"
                onClick={primaryCTA.onClick}
                className={`group font-medium ${styles.primaryButton}`}
              >
                {primaryCTA.text}
                <ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1" />
              </Button>
 
              {secondaryCTA && (
                <Button
                  size="lg"
                  variant="outline"
                  onClick={secondaryCTA.onClick}
                  className={`font-medium ${styles.secondaryButton}`}
                >
                  {secondaryCTA.text}
                </Button>
              )}
            </div>
          </div>
 
          {/* Image */}
          <div
            className={`relative ${imagePosition === "left" ? "lg:order-1" : "lg:order-2"}`}
          >
            <div className="relative aspect-[4/3] overflow-hidden rounded-2xl shadow-2xl">
              <Image
                src={imageUrl}
                alt={imageAlt}
                fill
                className="object-cover"
                sizes="(max-width: 768px) 100vw, 50vw"
              />
            </div>
            {/* Decorative element */}
            <div
              className={`absolute -bottom-6 -right-6 -z-10 h-full w-full rounded-2xl ${
                variant === "default"
                  ? "bg-primary/10"
                  : variant === "primary"
                    ? "bg-primary-foreground/10"
                    : "bg-white/10"
              }`}
            />
          </div>
        </div>
      </div>
    </section>
  );
}

Update the import paths to match your project setup.

import { Button } from "@/components/ui/button";

Usage

Basic Usage

import CTAImageSplit from "@/components/ui/cta-image-split";
 
export default function Page() {
  return (
    <CTAImageSplit
      headline="Transform your workflow"
      description="Join thousands of teams building better products."
      imageUrl="https://images.unsplash.com/photo-1522071820081-009f0129c71c"
      imageAlt="Team collaboration"
      primaryCTA={{
        text: "Get Started",
        onClick: () => {},
      }}
    />
  );
}

With Features List

<CTAImageSplit
  headline="Transform your workflow"
  description="Everything you need to succeed."
  features={[
    "Seamless integration with your tools",
    "Real-time collaboration",
    "Advanced analytics",
    "24/7 support",
  ]}
  imageUrl="https://images.unsplash.com/photo-1522071820081-009f0129c71c"
  primaryCTA={{ text: "Get Started", onClick: () => {} }}
/>

Image on Left

<CTAImageSplit
  headline="Ready to scale?"
  imageUrl="https://images.unsplash.com/photo-1551434678-e076c223a692"
  imagePosition="left"
  primaryCTA={{ text: "Get Started", onClick: () => {} }}
/>

Theme Variants

{/* Default theme */}
<CTAImageSplit
  variant="default"
  headline="Transform your workflow"
  imageUrl="..."
  primaryCTA={{ text: "Get Started", onClick: () => {} }}
/>
 
{/* Primary theme - Full brand color */}
<CTAImageSplit
  variant="primary"
  headline="Ready to scale?"
  imageUrl="..."
  primaryCTA={{ text: "Get Started", onClick: () => {} }}
/>
 
{/* Dark theme - Deep dark background */}
<CTAImageSplit
  variant="dark"
  headline="Join our community"
  imageUrl="..."
  primaryCTA={{ text: "Join Now", onClick: () => {} }}
/>

With Router Navigation

"use client";
 
import { useRouter } from "next/navigation";
import CTAImageSplit from "@/components/ui/cta-image-split";
 
export default function Page() {
  const router = useRouter();
 
  return (
    <CTAImageSplit
      headline="Ready to get started?"
      description="Start your 14-day free trial today."
      imageUrl="https://images.unsplash.com/photo-1522071820081-009f0129c71c"
      primaryCTA={{
        text: "Start Free Trial",
        onClick: () => router.push("/signup"),
      }}
      secondaryCTA={{
        text: "Watch Demo",
        onClick: () => router.push("/demo"),
      }}
    />
  );
}

Props

PropTypeDefaultDescription
headlinestringRequiredMain CTA headline
descriptionstringundefinedOptional description text
featuresstring[]undefinedOptional feature list with checkmarks
imageUrlstringRequiredImage URL (Unsplash, local, etc.)
imageAltstring"CTA Image"Image alt text for accessibility
imagePosition"left" | "right""right"Image placement
variant"default" | "primary" | "dark""default"Theme variant
primaryCTAobjectRequiredPrimary button configuration
secondaryCTAobjectundefinedOptional secondary button

TypeScript Interface

interface CTAImageSplitProps {
  headline: string;
  description?: string;
  features?: string[];
  imageUrl: string;
  imageAlt?: string;
  imagePosition?: "left" | "right";
  variant?: "default" | "primary" | "dark";
  primaryCTA: {
    text: string;
    href?: string;
    onClick?: () => void;
  };
  secondaryCTA?: {
    text: string;
    href?: string;
    onClick?: () => void;
  };
}

Image Sources

// Team collaboration
imageUrl="https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800&h=600&fit=crop"
 
// Business meeting
imageUrl="https://images.unsplash.com/photo-1551434678-e076c223a692?w=800&h=600&fit=crop"
 
// Technology/Laptop
imageUrl="https://images.unsplash.com/photo-1515378960530-7c0da6231fb1?w=800&h=600&fit=crop"
 
// Office workspace
imageUrl="https://images.unsplash.com/photo-1497366216548-37526070297c?w=800&h=600&fit=crop"

Local Images

import teamImage from "@/public/images/team.jpg";
 
<CTAImageSplit
  imageUrl={teamImage.src}
  // or with Next.js 13+
  imageUrl="/images/team.jpg"
/>

Customization

Theme Details

Default Theme:

  • Clean background
  • Standard text colors
  • Primary-colored checkmarks
  • Default button styles

Primary Theme:

  • Full brand color background
  • White/light text
  • Inverted buttons
  • Light checkmarks

Dark Theme:

  • Deep dark background (bg-slate-950)
  • White text
  • Bright white button
  • Light checkmarks

Custom Image Aspect Ratio

{/* Change aspect-[4/3] to your preferred ratio */}
<div className="relative aspect-square overflow-hidden rounded-2xl">
  <Image ... />
</div>

Remove Decorative Element

Remove this section from the component:

<div className="absolute -bottom-6 -right-6 -z-10 h-full w-full rounded-2xl bg-primary/10" />

Use Cases

Perfect for:

  • Product launches
  • Feature showcases
  • Service offerings
  • Team/company introductions
  • App downloads
  • Newsletter signups
  • Event registrations
  • Course enrollments