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
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
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
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
| Prop | Type | Default | Description |
|---|---|---|---|
headline | string | Required | Main CTA headline |
description | string | undefined | Optional description text |
features | string[] | undefined | Optional feature list with checkmarks |
imageUrl | string | Required | Image URL (Unsplash, local, etc.) |
imageAlt | string | "CTA Image" | Image alt text for accessibility |
imagePosition | "left" | "right" | "right" | Image placement |
variant | "default" | "primary" | "dark" | "default" | Theme variant |
primaryCTA | object | Required | Primary button configuration |
secondaryCTA | object | undefined | Optional 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