I built a simple hero section combining a logo marquee with a carousel.
It’s a common pattern in modern product pages. Logos for trust, carousel for showcasing visuals, but I wanted something easy to reuse and compose.
This was the result:
Here’s the component code:
'use client'
import Image from 'next/image'
import Balancer from 'react-wrap-balancer'
import type { LogoMarqueeItem } from '@/components/flx/blocks/logos/logo-marquee/logo-marquee'
import { LogoMarquee } from '@/components/flx/blocks/logos/logo-marquee/logo-marquee'
import type { CtaProps } from '@/components/flx/blocks/shared/cta/cta'
import { Cta } from '@/components/flx/blocks/shared/cta/cta'
import {
Carousel,
CarouselContent,
CarouselItem,
} from '@/components/ui/carousel'
import { cn } from '@/lib/utils'
export interface HeroLogosCarouselCarouselItem {
title: string
image: string
}
export interface HeroLogosCarouselProps {
title: string
description: string
logosInfo: string
primaryCTA?: CtaProps
secondaryCTA?: CtaProps
logos: LogoMarqueeItem[]
carouselItems: HeroLogosCarouselCarouselItem[]
className?: string
}
export function HeroLogosCarousel({
title,
description,
logosInfo,
primaryCTA,
secondaryCTA,
logos,
carouselItems,
className,
}: Readonly<HeroLogosCarouselProps>) {
const containerWidthClassName = cn('w-full max-w-6xl mx-auto px-4', className)
return (
<section className="space-y-15">
<div
className={cn(
'flex min-h-70 max-w-2xl flex-col justify-center gap-4',
containerWidthClassName,
)}
>
{title ? (
<h1 className="text-2xl font-medium tracking-tight md:text-4xl">
<Balancer balance={0.5}>{title}</Balancer>
</h1>
) : null}
{description ? (
<p className="text-muted-foreground max-w-2xl text-base whitespace-pre-line">
<Balancer balance={0.5}>{description}</Balancer>
</p>
) : null}
<div className="mt-3 flex flex-col gap-3 sm:flex-row">
{primaryCTA ? (
<Cta cta={primaryCTA} className="w-full sm:w-fit" />
) : null}
{secondaryCTA ? (
<Cta cta={secondaryCTA} className="w-full sm:w-fit" />
) : null}
</div>
</div>
{logos?.length || carouselItems?.length ? (
<div className="flex flex-col gap-10">
<div className={containerWidthClassName}>
<div className="flex flex-col gap-4 overflow-hidden sm:flex-row sm:items-center sm:justify-center">
<p className="text-muted-foreground text-center text-sm sm:max-w-45 sm:text-left">
{logosInfo}
</p>
<div className="w-full min-w-0 flex-1 sm:w-auto">
<LogoMarquee items={logos} />
</div>
</div>
</div>
<div className="relative w-full">
<Carousel
opts={{
align: 'center',
loop: false,
startIndex: Math.floor(carouselItems.length / 2),
}}
className="w-full"
aria-label="Image carousel"
>
<CarouselContent
className={cn(
'h-auto select-none first:!pl-0',
containerWidthClassName,
)}
>
{carouselItems.map((item, index) => (
<CarouselItem
key={`${item.title}-${index}`}
className="basis-full pl-4 md:basis-1/2 lg:basis-2/3"
>
<div className="bg-muted rounded-lg p-4">
<div className="relative aspect-video w-full overflow-hidden rounded-lg">
<Image
src={item.image}
alt={item.title}
fill
className="object-cover"
unoptimized
/>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</div>
</div>
) : null}
</section>
)
}
Usage example below:
import {
HeroLogosCarousel,
type HeroLogosCarouselProps,
} from './hero-logos-carousel'
export function HeroLogosCarouselExample() {
const values = {
title: 'Ambitious ideas deserve world-class design',
description:
'We partner with startups to craft products that stand out, win trust, and scale with speed.',
logosInfo: 'Our design crew worked with industry leaders',
primaryCTA: {
ctaEnabled: true,
text: 'Get Started',
link: '/',
variant: 'default',
},
secondaryCTA: {
ctaEnabled: true,
text: 'View Work',
link: '/',
variant: 'secondary',
},
logos: [
{
title: 'Supabase',
url: 'https://cdn.brandfetch.io/idsSceG8fK/w/800/h/156/theme/dark/logo.png?c=1dxbfHSJFAPEGdCLU4o5B',
},
{
title: 'Google',
url: 'https://cdn.brandfetch.io/id6O2oGzv-/theme/dark/logo.svg?c=1dxbfHSJFAPEGdCLU4o5B',
},
{
title: 'Shopify',
url: 'https://cdn.brandfetch.io/idAgPm7IvG/theme/dark/logo.svg?c=1dxbfHSJFAPEGdCLU4o5B',
},
{
title: 'Mongo',
url: 'https://cdn.brandfetch.io/ideyyfT0Lp/theme/dark/logo.svg?c=1dxbfHSJFAPEGdCLU4o5B',
},
{
title: 'LottieFiles',
url: 'https://cdn.brandfetch.io/idEExqEvR9/theme/dark/logo.svg?c=1dxbfHSJFAPEGdCLU4o5B',
},
],
carouselItems: [
{
title: 'Image 1',
image:
'https://images.unsplash.com/photo-1729575846511-f499d2e17d79?q=80&w=1332&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
{
title: 'Image 2',
image:
'https://images.unsplash.com/photo-1679193559674-860ef78899bc?q=80&w=1198&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
{
title: 'Image 3',
image:
'https://images.unsplash.com/photo-1771732266970-f63e35c8f47a?q=80&w=1332&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
},
],
} satisfies HeroLogosCarouselProps
return (
<HeroLogosCarousel
title={values.title}
description={values.description}
logosInfo={values.logosInfo}
primaryCTA={values.primaryCTA}
secondaryCTA={values.secondaryCTA}
logos={values.logos}
carouselItems={values.carouselItems}
/>
)
}
you can preview it here

Top comments (0)