CTA Banner
Full-width call-to-action card with title, description, and one or two actions.
Ready to ship?
Drop in the primitives, copy the blocks, keep your design system honest.
import { CTABanner } from '@/foundations/blocks/cta-banner';
import { Button } from '@/components/button';
export default function CTABannerPreview() {
return (
<CTABanner
layout="side"
title="Ready to ship?"
description="Drop in the primitives, copy the blocks, keep your design system honest."
action={<Button>Get started</Button>}
secondaryAction={<Button variant="outline">Talk to us</Button>}
/>
);
}
export const meta = {
layout: 'fullscreen',
}; Dependencies
Source Code
import { Container } from '@/components/container';
import { cn } from '@/lib/utils/classnames';
interface CTABannerProps
extends Omit<React.ComponentPropsWithRef<'section'>, 'title'> {
title: React.ReactNode;
description?: React.ReactNode;
/** Primary action — typically a `<Button>`. */
action?: React.ReactNode;
/** Optional secondary action. */
secondaryAction?: React.ReactNode;
/** Optional background image URL — applied with cover/center positioning. */
backgroundImage?: string;
/** Layout — `'side'` puts copy and CTAs in two columns on lg+. */
layout?: 'stacked' | 'side';
}
const CTABanner = ({
ref,
className,
title,
description,
action,
secondaryAction,
backgroundImage,
layout = 'stacked',
...rest
}: CTABannerProps) => (
<section
ref={ref}
className={cn('w-full py-16 md:py-24', className)}
{...rest}
>
<Container>
<div
style={
backgroundImage
? {
backgroundImage: `url(${backgroundImage})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}
: undefined
}
className={cn(
'flex flex-col gap-6 bg-background p-8 md:p-12 lg:p-16',
layout === 'side' && 'lg:flex-row lg:items-center lg:justify-between'
)}
>
<div
className={cn(
'flex flex-col gap-3',
layout === 'side' && 'max-w-2xl'
)}
>
<h2 className="text-balance font-semibold text-3xl md:text-4xl lg:text-5xl">
{title}
</h2>
{description && (
<p className="text-pretty text-xl md:text-2xl">{description}</p>
)}
</div>
{(action || secondaryAction) && (
<div className="flex flex-wrap gap-3">
{action}
{secondaryAction}
</div>
)}
</div>
</Container>
</section>
);
export type { CTABannerProps };
export { CTABanner }; CTABanner is the “got a project? let’s talk.” block — a rounded, bordered
card with a heading, description, and one or two action buttons. Supports an
optional background image and a side-by-side layout on large viewports.
Anatomy
<CTABanner
title="Ready to ship?"
description="Drop in the primitives, copy the blocks, keep your design system honest."
action={<Button>Get started</Button>}
secondaryAction={<Button variant="secondary">Talk to us</Button>}
/>
API Reference
Extends the section element.
| Prop | Default | Type |
|---|---|---|
title * | - | ReactNode |
description | - | ReactNode |
action | - | ReactNode |
secondaryAction | - | ReactNode |
backgroundImage | - | string |
layout | 'stacked' | 'stacked''side' |
Examples
Default
Ready to ship?
Drop in the primitives, copy the blocks, keep your design system honest.
import { CTABanner } from '@/foundations/blocks/cta-banner';
import { Button } from '@/components/button';
export default function CTABannerPreview() {
return (
<CTABanner
layout="side"
title="Ready to ship?"
description="Drop in the primitives, copy the blocks, keep your design system honest."
action={<Button>Get started</Button>}
secondaryAction={<Button variant="outline">Talk to us</Button>}
/>
);
}
export const meta = {
layout: 'fullscreen',
}; Previous
Core Values
Next
FAQs