A swipeable row of arched portraits on a dark band with a hover gold ring, age badge, name and details. React and Tailwind CSS.
ArchedPortraitCarousel.tsx
// Row of arched portraits on a dark band: the photo slowly zooms and a gold
// ring draws in on hover, with an age badge, label, name and bloodline.
// Swipe or scroll sideways (native scroll-snap). Pure Tailwind, no dependencies. Works with Tailwind v3 and v4.
type Portrait = { name: string; label: string; line: string; facts: string; age: number; image: string; href?: string };
type Props = {
heading?: string;
intro?: string;
items?: Portrait[];
};
const img = (id: string) => `https://images.unsplash.com/photo-${id}?w=800&q=80&auto=format&fit=crop`;
const DEFAULT_ITEMS: Portrait[] = [
{ name: "Aurelian", label: "Stallion", line: "By Cassini × Quidam", facts: "Holsteiner · 168 cm · Grey", age: 11, image: img("1588424978994-25105f27bb92") },
{ name: "Nocturne", label: "Stallion", line: "By Diamant × Corland", facts: "KWPN · 170 cm · Black", age: 9, image: img("1645767006495-0136265e26c0") },
{ name: "Saffi", label: "Broodmare", line: "By Calido × Lordanos", facts: "Oldenburg · 165 cm · Bay", age: 13, image: img("1692024363479-1648f19f240f") },
{ name: "Kestrel", label: "Young horse", line: "By Nocturne × Aurelia", facts: "KWPN · 162 cm · Black", age: 4, image: img("1757496798794-4b19ef24951c") },
{ name: "Marigold", label: "For sale", line: "By Aurelian × Saffi", facts: "Holsteiner · 160 cm · Chestnut", age: 5, image: img("1569882200827-65d20bd7da12") },
];
export default function ArchedPortraitCarousel({
heading = "Our stallions",
intro = "Proven bloodlines, gentle temperaments and the scope for the highest levels of sport.",
items = DEFAULT_ITEMS,
}: Props) {
return (
<section className="overflow-hidden bg-[#210313] py-28 font-['Hanken_Grotesk',ui-sans-serif,system-ui,sans-serif] text-white antialiased md:py-40">
<div className="mx-auto grid max-w-[100rem] gap-8 px-4 md:px-8 lg:grid-cols-12 lg:items-end lg:px-12">
<h2 className="font-['Fraunces',Georgia,'Times_New_Roman',serif] text-[clamp(2.25rem,4.5vw,4.75rem)] leading-none tracking-[-0.03em] lg:col-span-7">{heading}</h2>
<p className="text-lg leading-relaxed text-white/65 lg:col-span-4 lg:col-start-9">{intro}</p>
</div>
<ul className="mt-16 flex snap-x snap-mandatory gap-6 overflow-x-auto px-4 pb-4 pt-4 [scrollbar-width:none] md:mt-24 md:px-8 lg:px-12 [&::-webkit-scrollbar]:hidden">
{items.map((h) => (
<li key={h.name} className="w-[80%] shrink-0 snap-start px-4 sm:w-[46%] lg:w-[30%] xl:w-[26%]">
<a href={h.href ?? "#"} className="group block text-center">
<div className="relative">
<div className="relative aspect-[3/4] overflow-hidden rounded-t-full bg-[#2c081a]">
<img src={h.image} alt={h.name} loading="lazy" className="absolute inset-0 h-full w-full object-cover transition-transform duration-[1800ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-[1.07]" />
</div>
<div aria-hidden className="pointer-events-none absolute -inset-2.5 rounded-t-full border border-[#dcc59a]/50 opacity-0 transition-all duration-1000 group-hover:-inset-4 group-hover:opacity-100" />
<span className="absolute bottom-6 right-0 grid h-16 w-16 translate-x-1/4 place-items-center rounded-full bg-[#dcc59a] text-center leading-none text-[#210313]">
<span>
<span className="block font-['Fraunces',Georgia,serif] text-xl">{h.age}</span>
<span className="block text-[0.7rem] font-medium">years</span>
</span>
</span>
</div>
<p className="mt-8 text-xs font-medium uppercase tracking-[0.12em] text-[#dcc59a]">{h.label}</p>
<h3 className="mt-3 font-['Fraunces',Georgia,serif] text-4xl md:text-5xl">{h.name}</h3>
<p className="mt-3 font-['Fraunces',Georgia,serif] text-lg italic text-white/65">{h.line}</p>
<p className="mt-4 text-xs font-medium uppercase tracking-[0.12em] text-white/45">{h.facts}</p>
</a>
</li>
))}
</ul>
</section>
);
}
How to use it
- Make sure your Next.js or React project has Tailwind CSS set up (v3 or v4 both work).
- Create
components/ArchedPortraitCarousel.tsxand paste the code. - Import it into a page and replace the sample text and images with your own.
