All-Access · 23 templates$1319.97$99/year — save 92%Get access
  • HomeHome
  • TemplatesTemplates17+
  • ComponentsComponents80+
  • PricingPricing
  • Need a Website?Need a Website?New
Unlock All AccessUnlock All Access

Our latest template collection

Ready-to-launch sites for real estate agents, service businesses, and local brands — built to rank, load fast, and convert.

Explore All TemplatesExplore All Templates

Orvaine - Luxury Equestrian & Horse Breeding Website Template

Orvaine is a premium website template for stud farms, sport horse breeders and equestrian estates: horse pages with pedigrees, events, films. Next.js, Astro, Sanity.

Download Template
Orvaine - Luxury Equestrian & Horse Breeding Website Template

Maressa - Luxury Yacht Charter & Sales Website Template

Maressa is a premium website template for yacht charter, brokerage and superyacht brands: 21 editable sections, full-screen films, Next.js and Astro versions. Sanity.

Download Template
Maressa - Luxury Yacht Charter & Sales Website Template

Krishi – Agriculture & Farm Website Template

Krishi is a premium website template for farms, agritech and food brands: 17 editable sections, cinematic GSAP motion and both Next.js and Astro versions. Built with Sanity.

Download Template
Krishi – Agriculture & Farm Website Template
ComponentsGalleryExpanding Image Panels
FreeReactTailwind CSSNext.js

Expanding Image Panels

Tall image panels that widen on hover to reveal their text, turning into a swipeable row on phones. React and Tailwind CSS, no JavaScript.

View codeView code
Full screen
ExpandingImagePanels.tsx
// Tall image panels that widen on hover (desktop) and reveal their text;
// a sideways-scrolling row of cards on phones and tablets.
// Pure Tailwind, no dependencies. Works with Tailwind v3 and v4.

type Panel = { name: string; region?: string; text?: string; image: string; href?: string };

type Props = {
  heading?: string[];
  intro?: string;
  panels?: Panel[];
};

const img = (id: string) => `https://images.unsplash.com/photo-${id}?w=1400&q=80&auto=format&fit=crop`;

const DEFAULT_PANELS: Panel[] = [
  { name: "Amalfi Coast", region: "Italy · Mediterranean", text: "Lemon groves, cliffside villages and long lunches at anchor off Positano.", image: img("1612698093158-e07ac200d44e") },
  { name: "French Riviera", region: "France & Monaco", text: "From Saint-Tropez to Monaco: the original stage for life on the water.", image: img("1595138320174-a64d168e9970") },
  { name: "Dalmatian Coast", region: "Croatia · Adriatic", text: "A thousand islands, Venetian towns and the clearest water in Europe.", image: img("1565784623727-8cebf4712b2d") },
  { name: "The Grenadines", region: "Caribbean", text: "Turquoise reefs, private islands and the trade winds of winter.", image: img("1484821582734-6c6c9f99a672") },
  { name: "The Maldives", region: "Indian Ocean", text: "Twenty-six atolls of sandbanks, manta rays and absolute stillness.", image: img("1514282401047-d79a71a590e8") },
];

export default function ExpandingImagePanels({
  heading = ["Five seas,", "one standard"],
  intro = "From the Riviera in June to the Grenadines at Christmas, our fleet follows the sun.",
  panels = DEFAULT_PANELS,
}: Props) {
  return (
    <section className="bg-white py-28 font-['Helvetica_Neue',Helvetica,Arial,sans-serif] text-[#0d1114] antialiased md:py-40">
      <div className="mx-auto grid max-w-[100rem] gap-8 px-4 md:gap-12 md:px-8 lg:grid-cols-12 lg:items-end lg:px-12">
        <h2 className="text-[clamp(2.25rem,4.5vw,4.75rem)] font-normal leading-none tracking-[-0.035em] lg:col-span-7">
          {heading.map((line, i) => (
            <span key={i} className="block">
              {line}
            </span>
          ))}
        </h2>
        {intro && <p className="text-base leading-relaxed text-[#0d1114]/60 md:text-lg lg:col-span-4 lg:col-start-9 lg:pb-3">{intro}</p>}
      </div>

      <div className="mt-14 flex snap-x snap-mandatory gap-3 overflow-x-auto px-4 [scrollbar-width:none] md:mt-20 md:px-8 lg:h-[78vh] lg:min-h-[36rem] lg:gap-2 lg:overflow-visible lg:px-12 [&::-webkit-scrollbar]:hidden">
        {panels.map((p, i) => (
          <a
            key={p.name}
            href={p.href ?? "#"}
            className="group relative isolate block aspect-[3/4] w-[78vw] shrink-0 snap-start overflow-hidden bg-[#111c25] text-white transition-[flex-grow] duration-[1200ms] ease-[cubic-bezier(0.22,1,0.36,1)] sm:w-[46vw] lg:aspect-auto lg:w-auto lg:flex-1 lg:hover:flex-[2.6]"
          >
            <img
              src={p.image}
              alt={p.name}
              loading="lazy"
              className="absolute inset-0 -z-10 h-full w-full object-cover transition-transform duration-[1600ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-105"
            />
            <div className="absolute inset-0 -z-10 bg-gradient-to-t from-[#060b10]/80 via-[#060b10]/10 to-[#060b10]/20" />
            <span className="absolute left-6 top-6 text-xs tabular-nums text-white/70">{String(i + 1).padStart(2, "0")}</span>
            <span className="absolute right-5 top-5 grid h-11 w-11 place-items-center rounded-full border border-white/40 opacity-0 transition-opacity duration-700 group-hover:opacity-100">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.1} aria-hidden className="h-4 w-4">
                <path d="M7 17 17 7M8 7h9v9" />
              </svg>
            </span>
            <div className="absolute inset-x-0 bottom-0 p-6 lg:p-8">
              {p.region && (
                <p className="mb-3 text-xs font-medium uppercase tracking-[0.08em] text-white/70 lg:opacity-0 lg:transition-opacity lg:duration-700 lg:group-hover:opacity-100">
                  {p.region}
                </p>
              )}
              <h3 className="text-4xl font-normal leading-[0.95] lg:text-[clamp(2rem,2.6vw,3rem)]">{p.name}</h3>
              {p.text && (
                <p className="mt-4 max-w-sm text-sm leading-relaxed text-white/75 lg:max-h-0 lg:overflow-hidden lg:opacity-0 lg:transition-all lg:duration-700 lg:group-hover:max-h-32 lg:group-hover:opacity-100">
                  {p.text}
                </p>
              )}
            </div>
          </a>
        ))}
      </div>
    </section>
  );
}

How to use it

  1. Make sure your Next.js or React project has Tailwind CSS set up.
  2. Create components/ExpandingImagePanels.tsx and paste the code.
  3. Import it into a page and replace the placeholder text and images with your own.
Maressa - Luxury Yacht Charter & Sales Website Template
From the full template
Maressa - Luxury Yacht Charter & Sales Website Template$79

This section is one part of Maressa - Luxury Yacht Charter & Sales Website Template: a complete website with every page, Sanity CMS and SEO already set up.

Get the full template Get the full template Live preview Live preview

More free components

All gallery →
Count-Up Stats

Count-Up Stats

Image Trails

Fading Quote Testimonials

Fading Quote Testimonials

Ready for a faster site that's easier to edit?

Book a free 30-minute call. We'll review your current site and send a fixed-price plan. No pressure, no pitch.

Book Free Call Book Free Call No commitment required

Fast Next.js + Sanity websites, built for you or ready to launch with our templates.

Book a Free Call Book a Free Call
Services
  • Web Design
  • UI/UX Design
  • Framer Dev
  • Webflow Dev
  • Custom Dev
  • Sanity CMS
Resources
  • Templates
  • Pricing
  • License
  • Components
  • Case Studies
Company
  • About Us
  • Contact
  • Privacy Policy
© 2026 rayso.studio — All rights reservedDesigned & built in-house Remote-first agency
Let's TalkLet's Talk