Cards

CTA Card

A responsive CTA card with an image, text, and an email subscription form.

A bright, minimalist workspace with a laptop, monitor, and design accessories.
Streamline Your Creative Workflow

Installation

CLI

Installation via the CLI is coming soon. For now, please follow the manual installation instructions below.

Manual

No external dependencies are required. Copy and paste the following code into components/ui/cta-card.tsx.

'use client';

import { cn } from '@/lib/utils';
import Image from 'next/image';

import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useState } from 'react';

interface CTACardProps {
  /** The main headline or title for the CTA. */
  title: string;
  /** A short description supporting the title. */
  description: string;
  /** The source URL for the image. */
  imageSrc: string;
  /** Alt text for the image, for accessibility. */
  imageAlt?: string;
  /** Placeholder text for the email input field. */
  inputPlaceholder?: string;
  /** Text displayed on the submission button. */
  buttonText?: string;
  /** Callback function executed when the form is submitted. */
  onSubmit: (email: string) => void;
  /** Optional additional class names to apply to the root card element for custom styling. */
  className?: string;
}

/**
 * A responsive Call-to-Action (CTA) card component.
 * It displays an image alongside a title, description, and an email subscription form.
 * Designed to be theme-aware and fully customizable through props.
 */
export function CTACard({
  title,
  description,
  imageSrc,
  imageAlt = 'Promotional image',
  inputPlaceholder = 'name@email.com',
  buttonText = 'Subscribe',
  onSubmit,
  className,
}: CTACardProps) {
  const [email, setEmail] = useState('');

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    onSubmit(email);
    setEmail('');
  };

  return (
    <Card
      className={cn(
        'w-full max-w-4xl overflow-hidden p-3 border rounded-3xl',
        className
      )}
    >
      {/* 
        DECISION: A 16-column grid is used on desktop to allow for fine-grained,
        asymmetrical layouts (e.g., the 7/9 split used here) that a standard
        12-column grid might not accommodate as cleanly.
      */}
      <div className='grid grid-cols-1 md:grid-cols-16 gap-3'>
        <div className='relative h-64 md:h-auto rounded-2xl md:col-span-7'>
          <Image
            src={imageSrc}
            alt={imageAlt}
            fill
            className='object-cover rounded-2xl'
          />
        </div>

        <div className='flex flex-col w-full justify-start items-start rounded-2xl border md:col-span-9 p-6 md:p-8 gap-8 md:gap-16 lg:gap-20 bg-secondary'>
          <CardHeader className='w-full p-0'>
            <CardTitle className='text-2xl md:text-3xl font-bold'>
              {title}
            </CardTitle>
          </CardHeader>
          <CardContent className='flex w-full flex-col gap-2 p-0'>
            <Label className='text-muted-foreground'>{description}</Label>
            <form
              onSubmit={handleSubmit}
              className='mt-2 flex w-full flex-col sm:flex-row sm:space-x-2 space-y-2 sm:space-y-0'
            >
              <Input
                type='email'
                placeholder={inputPlaceholder}
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                className='flex-1 text-base'
                aria-label='Email for newsletter'
                required
              />
              <Button type='submit'>{buttonText}</Button>
            </form>
          </CardContent>
        </div>
      </div>
    </Card>
  );
}

Usage

Import the component and provide the required props to render the CTA card.

import { CTACard } from '@/components/ui/cta-card';

export default function MyPage() {
  const handleSubscription = (email: string) => {
    // Replace with your actual subscription logic
    alert(`Subscribing with: ${email}`);
  };

  return (
    <div className='w-full p-4 flex flex-col items-center gap-8 bg-background'>
      {/* Example: Customized Usage */}
      <CTACard
        title='Unlock Exclusive Content'
        description='Become a premium member to access our full library of resources and tools.'
        imageSrc='https://images.unsplash.com/photo-1611974789855-9c2a0a7236a3'
        imageAlt='Abstract financial graph'
        inputPlaceholder='your-best@email.com'
        buttonText='Get Access Now'
        onSubmit={handleSubscription}
        className='border-primary'
      />
    </div>
  );
}

Props

PropTypeDefaultDescription
titlestringRequiredThe main headline or title for the CTA.
descriptionstringRequiredA short description supporting the title.
imageSrcstringRequiredThe source URL for the image.
imageAltstring'Promotional image'Alt text for the image, for accessibility.
inputPlaceholderstring'name@email.com'Placeholder text for the email input field.
buttonTextstring'Subscribe'Text displayed on the submission button.
onSubmit(email: string) => voidRequiredCallback function executed when the form is submitted.
classNamestringOptional additional class names to apply to the root card element for custom styling.