Web InnoventixFreeCode

Flip H Effect

Original · free

Image flips horizontally to a caption back on hover.

byWeb InnoventixReact + Tailwind
fliphimageeffect
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/imgfx-flip-h.json
imgfx-flip-h.tsx
"use client";

import { useId } from "react";

type FlipItem = {
  src: string;
  alt: string;
  title: string;
  caption: string;
  meta: string;
};

const ITEMS: FlipItem[] = [
  {
    src: "/img/gallery/g07.webp",
    alt: "Sunlight breaking through a dense forest canopy",
    title: "Understory",
    caption: "Shot at first light, when the canopy filters the sun into soft vertical beams.",
    meta: "Forest · 35mm",
  },
  {
    src: "/img/gallery/g14.webp",
    alt: "Calm coastline with waves meeting pale sand",
    title: "Low Tide",
    caption: "A long exposure smooths the surf into a single sheet of moving glass.",
    meta: "Coast · 24mm",
  },
  {
    src: "/img/gallery/g22.webp",
    alt: "City street lit by neon signage after rain",
    title: "Afterglow",
    caption: "Wet asphalt doubles every sign, so the street reads twice as bright.",
    meta: "Street · 50mm",
  },
];

export default function ImgfxFlipH() {
  const uid = useId().replace(/[:]/g, "");
  const prefix = `imgfxfliph-${uid}`;

  return (
    <section className="relative w-full bg-slate-50 px-6 py-20 dark:bg-slate-950 sm:px-10 lg:py-28">
      <style>{`
        .${prefix}-scene {
          perspective: 1400px;
        }
        .${prefix}-card {
          transform-style: preserve-3d;
          transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
          will-change: transform;
        }
        .${prefix}-scene:hover .${prefix}-card,
        .${prefix}-scene:focus-within .${prefix}-card {
          transform: rotateY(180deg);
        }
        .${prefix}-face {
          backface-visibility: hidden;
          -webkit-backface-visibility: hidden;
        }
        .${prefix}-back {
          transform: rotateY(180deg);
        }
        @media (prefers-reduced-motion: reduce) {
          .${prefix}-card {
            transition-duration: 0.001ms;
          }
        }
      `}</style>

      <div className="mx-auto max-w-6xl">
        <header className="mx-auto mb-14 max-w-2xl text-center">
          <p className="text-xs font-semibold uppercase tracking-[0.28em] text-indigo-600 dark:text-indigo-400">
            Image Effect · Horizontal Flip
          </p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-slate-900 dark:text-slate-50 sm:text-4xl">
            Flip to the Story
          </h2>
          <p className="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
            Each frame turns on its vertical axis to reveal the caption behind it.
            <span className="ml-1 font-medium text-slate-800 dark:text-slate-200">
              Hover or focus a photo to flip it.
            </span>
          </p>
        </header>

        <ul className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
          {ITEMS.map((item) => (
            <li key={item.src} className={`${prefix}-scene`}>
              <div
                tabIndex={0}
                role="group"
                aria-label={`${item.title}. ${item.caption}`}
                className={`${prefix}-card relative h-80 w-full rounded-2xl outline-none ring-offset-2 ring-offset-slate-50 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:ring-offset-slate-950`}
              >
                {/* Front face */}
                <div
                  className={`${prefix}-face absolute inset-0 overflow-hidden rounded-2xl border border-slate-200 shadow-lg shadow-slate-900/5 dark:border-slate-800 dark:shadow-black/40`}
                >
                  {/* eslint-disable-next-line @next/next/no-img-element */}
                  <img
                    src={item.src}
                    alt={item.alt}
                    loading="lazy"
                    draggable={false}
                    className="h-full w-full object-cover"
                  />
                  <div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-between gap-3 bg-gradient-to-t from-slate-950/80 via-slate-950/30 to-transparent p-4">
                    <span className="text-sm font-semibold text-white drop-shadow">
                      {item.title}
                    </span>
                    <span className="rounded-full bg-white/15 px-2.5 py-1 text-[10px] font-medium uppercase tracking-wider text-white backdrop-blur-sm">
                      Flip
                    </span>
                  </div>
                </div>

                {/* Back face */}
                <div
                  className={`${prefix}-face ${prefix}-back absolute inset-0 flex flex-col justify-between overflow-hidden rounded-2xl border border-indigo-200 bg-gradient-to-br from-indigo-50 via-white to-violet-50 p-6 shadow-lg shadow-indigo-900/10 dark:border-indigo-500/30 dark:from-slate-900 dark:via-slate-900 dark:to-indigo-950/60 dark:shadow-black/40`}
                >
                  <div>
                    <p className="text-[10px] font-semibold uppercase tracking-[0.28em] text-indigo-600 dark:text-indigo-400">
                      {item.meta}
                    </p>
                    <h3 className="mt-3 text-xl font-semibold tracking-tight text-slate-900 dark:text-slate-50">
                      {item.title}
                    </h3>
                    <p className="mt-3 text-sm leading-relaxed text-slate-600 dark:text-slate-300">
                      {item.caption}
                    </p>
                  </div>
                  <div className="flex items-center gap-2 text-xs font-medium text-indigo-600 dark:text-indigo-400">
                    <span
                      aria-hidden="true"
                      className="inline-block h-px w-8 bg-indigo-400/60 dark:bg-indigo-500/50"
                    />
                    Behind the frame
                  </div>
                </div>
              </div>
            </li>
          ))}
        </ul>
      </div>
    </section>
  );
}

Dependencies

None (React + Tailwind only).

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →