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
ComponentsCarouselMenu Card Slider
FreeReactTailwind CSSNext.js

Menu Card Slider

A restaurant menu slider: an intro card then dish cards with photo, price and description, moved with square arrows. React and Tailwind CSS.

View codeView code
Full screen
MenuCardSlider.tsx
"use client";

// Menu slider: an intro card followed by dish cards (photo, name, price,
// description) that slide one step at a time with square arrow buttons.
// Pure Tailwind + React, no carousel library. Works with Tailwind v3 and v4.

import { useEffect, useState } from "react";

type Dish = { name: string; price: string; description: string; image: string };

type Props = {
  eyebrow?: string;
  heading?: string[];
  intro?: string;
  dishes?: Dish[];
};

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

const DEFAULT_DISHES: Dish[] = [
  { name: "Duck confit", price: "$32", description: "Slow-rendered duck leg, celeriac purée, pickled cherry, thyme jus", image: img("1414235077428-338989a2e8c0") },
  { name: "Bouillabaisse", price: "$54", description: "Marseille-style, saffron broth, rouille, grilled sourdough", image: img("1547592166-23ac45744acd") },
  { name: "Côte de boeuf", price: "$68", description: "28-day dry-aged, bordelaise sauce, pommes sarladaises", image: img("1529692236671-f1f6cf9683ba") },
  { name: "Garden salad", price: "$18", description: "Market leaves, shaved fennel, toasted seeds, sherry dressing", image: img("1540189549336-e6e99c3679fe") },
];

const Arrow = ({ flip = false }: { flip?: boolean }) => (
  <svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden className={flip ? "rotate-180" : ""}>
    <path d="M7 4l6 6-6 6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
  </svg>
);

// Cards visible at once per breakpoint (matches the w-full / md:w-1/2 / lg:w-1/3 widths below)
function useVisible() {
  const [visible, setVisible] = useState(1);
  useEffect(() => {
    const update = () => setVisible(window.innerWidth >= 1024 ? 3 : window.innerWidth >= 768 ? 2 : 1);
    update();
    window.addEventListener("resize", update);
    return () => window.removeEventListener("resize", update);
  }, []);
  return visible;
}

export default function ConveyorBeltSlider({
  eyebrow = "From the kitchen",
  heading = ["Signature", "plates"],
  intro = "Great food begins long before it reaches the plate: in our relationships with local farmers, the hours of preparation and a deep respect for classical technique.",
  dishes = DEFAULT_DISHES,
}: Props) {
  const visible = useVisible();
  const total = dishes.length + 1; // intro card + dishes
  const maxIndex = Math.max(0, total - visible);
  const [index, setIndex] = useState(0);
  const current = Math.min(index, maxIndex);

  const button = "grid h-14 w-14 place-items-center border border-[#0F4729]/30 bg-white text-[#0F4729] transition-colors duration-200 hover:border-[#0F4729] hover:bg-[#0F4729] hover:text-white disabled:pointer-events-none disabled:opacity-40";

  return (
    <section className="bg-[#FFF7EA] px-5 py-20 font-['Inter',ui-sans-serif,system-ui,sans-serif] text-[#1c1917] antialiased md:px-10 md:py-28">
      <div className="mx-auto max-w-7xl">
        <div className="overflow-hidden">
          <div className="-ml-6 flex transition-transform duration-500 ease-out" style={{ transform: `translateX(-${(current * 100) / visible}%)` }}>
            <div className="w-full shrink-0 pl-6 md:w-1/2 lg:w-1/3">
              <div className="lg:pr-8">
                <p className="mb-4 text-xs font-bold uppercase tracking-[0.2em] text-[#0F4729]">{eyebrow}</p>
                <h2 className="mb-6 font-['Bricolage_Grotesque',ui-sans-serif,system-ui,sans-serif] text-[clamp(2.5rem,4vw,4rem)] font-bold uppercase leading-[1.1] tracking-tight text-[#0F4729]">
                  {heading.map((line) => (
                    <span key={line} className="block">
                      {line}
                    </span>
                  ))}
                </h2>
                <p className="mb-10 text-lg font-medium text-stone-500">{intro}</p>
                <div className="flex gap-3">
                  <button type="button" onClick={() => setIndex(current - 1)} disabled={current === 0} aria-label="Previous" className={button}>
                    <Arrow flip />
                  </button>
                  <button type="button" onClick={() => setIndex(current + 1)} disabled={current === maxIndex} aria-label="Next" className={button}>
                    <Arrow />
                  </button>
                </div>
              </div>
            </div>

            {dishes.map((d) => (
              <article key={d.name} className="group w-full shrink-0 pl-6 md:w-1/2 lg:w-1/3">
                <div className="relative h-[420px] w-full overflow-hidden">
                  <img src={d.image} alt={d.name} loading="lazy" className="absolute inset-0 h-full w-full object-cover transition-transform duration-700 group-hover:scale-105" />
                </div>
                <div className="py-6">
                  <div className="mb-2 flex items-start justify-between gap-4">
                    <h3 className="text-lg font-black uppercase tracking-widest text-[#0F4729]">{d.name}</h3>
                    <span className="text-lg font-bold text-[#0F4729]">{d.price}</span>
                  </div>
                  <p className="text-base leading-relaxed text-stone-600">{d.description}</p>
                </div>
              </article>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

How to use it

  1. Make sure your Next.js or React project has Tailwind CSS set up.
  2. Create components/MenuCardSlider.tsx and paste the code.
  3. Import it into a page and replace the placeholder text and images with your own.
Maison - Restaurant & Cafe Next.js Template
From the full template
Maison - Restaurant & Cafe Next.js Template$49

This section is one part of Maison - Restaurant & Cafe Next.js 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 carousel →
Fading Quote Testimonials

Fading Quote Testimonials

Press Marquee

Press Marquee

Infinite Image Carousel

Infinite Image Carousel

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