A photo frame with two white panels, a big rating with stacked avatars, and a transparent window cut into one panel. React and Tailwind CSS.
SpotlightWindowPanels.tsx
// Photo frame with two white panels: links, a big rating and overlapping
// avatars on the left; perks and a transparent "window" onto the photo on the
// right (a huge white box-shadow paints the panel around it).
// Pure Tailwind, no dependencies. Works with Tailwind v3 and v4.
type Link = { label: string; href: string };
type Props = {
heading?: string;
links?: Link[];
score?: string;
scoreLabel?: string;
avatars?: string[];
panelHeading?: string;
perks?: Link[];
ctaHeading?: string;
cta?: Link;
image?: string;
};
const face = (id: string) => `https://images.unsplash.com/photo-${id}?w=200&h=200&q=80&auto=format&fit=crop&crop=faces`;
const DEFAULT_LINKS: Link[] = [
{ label: "Shop skincare", href: "#" },
{ label: "Find your routine", href: "#" },
{ label: "Book a consultation", href: "#" },
];
const DEFAULT_PERKS: Link[] = [
{ label: "Free shipping over $50", href: "#" },
{ label: "30-day returns", href: "#" },
{ label: "Samples with every order", href: "#" },
];
function LinkList({ items }: { items: Link[] }) {
return (
<ul className="mt-8 border-t border-[#cbcbcb]">
{items.map((l) => (
<li key={l.label} className="border-b border-[#cbcbcb]">
<a href={l.href} className="group flex items-center justify-between py-4 text-lg">
{l.label}
<span aria-hidden className="transition-transform group-hover:translate-x-1">→</span>
</a>
</li>
))}
</ul>
);
}
export default function SpotlightWindow({
heading = "Skincare that respects your skin",
links = DEFAULT_LINKS,
score = "4.9",
scoreLabel = "from 12,000 reviews",
avatars = [face("1611451444023-7fe9d86fe1d0"), face("1531123897727-8f129e1688ce"), face("1438761681033-6461ffad8d80")],
panelHeading = "Why people switch",
perks = DEFAULT_PERKS,
ctaHeading = "Your first order, 15% off",
cta = { label: "Shop now", href: "#" },
image = "https://images.unsplash.com/photo-1600334129128-685c5582fd35?w=2400&q=80&auto=format&fit=crop",
}: Props) {
return (
<section className="bg-white px-4 py-10 font-['Inter',ui-sans-serif,system-ui,sans-serif] text-black antialiased md:px-6 md:py-16">
<div className="relative isolate mx-auto flex max-w-[87rem] flex-col gap-3 overflow-hidden rounded-3xl p-3 md:flex-row md:justify-between md:gap-7 md:p-7 lg:h-[56rem]">
<img src={image} alt="" className="absolute inset-0 -z-10 h-full w-full object-cover" />
<div className="flex flex-col rounded-2xl bg-white px-6 pb-8 pt-10 md:flex-1 md:px-7 md:pb-14 md:pt-12 lg:w-[26rem] lg:flex-none">
<h2 className="max-w-[22rem] font-['Prata',Georgia,serif] text-3xl leading-10">{heading}</h2>
<LinkList items={links} />
<div className="mt-12 lg:mt-auto">
<span className="block font-['Prata',Georgia,serif] text-[clamp(4rem,8vw,7rem)] leading-none">{score}</span>
<p className="mt-1 font-['Prata',Georgia,serif] text-2xl">{scoreLabel}</p>
<div className="mt-6 flex -space-x-12 transition-all hover:-space-x-5">
{avatars.map((a) => (
<img key={a} src={a} alt="" className="h-24 w-24 rounded-full object-cover ring-2 ring-white transition-transform duration-500 hover:-translate-y-2" />
))}
</div>
</div>
</div>
<div className="relative flex min-h-[42rem] flex-col overflow-hidden rounded-2xl md:min-h-0 md:flex-1 lg:w-[26rem] lg:flex-none">
<div className="relative z-10 px-6 pt-10 md:px-7 md:pt-12">
<h2 className="max-w-[22rem] font-['Prata',Georgia,serif] text-3xl leading-10">{panelHeading}</h2>
<LinkList items={perks} />
</div>
{/* The "window": transparent box, with a huge white shadow painting the panel around it */}
<div className="relative m-5 mt-11 flex flex-1 flex-col justify-end rounded-2xl bg-gradient-to-t from-black/45 via-black/10 to-transparent p-5 pb-8 shadow-[0_0_0_100vmax_#ffffff] md:min-h-[26rem]">
<p className="max-w-[18rem] px-1 font-['Prata',Georgia,serif] text-4xl leading-tight text-white">{ctaHeading}</p>
<a href={cta.href} className="mt-6 block w-full rounded-full bg-black px-6 py-4 text-center text-sm font-medium text-white transition-colors hover:bg-neutral-800">
{cta.label}
</a>
</div>
</div>
</div>
</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/SpotlightWindowPanels.tsxand paste the code. - Import it into a page and replace the sample text and images with your own.
