Web InnoventixFreeCode

Skew Hover Effect

Original · free

Image skews subtly and lifts on hover.

byWeb InnoventixReact + Tailwind
skewhoverimageeffect
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-skew-hover.json
imgfx-skew-hover.tsx
"use client";

import { useState } from "react";

type SkewCard = {
  src: string;
  alt: string;
  label: string;
  tilt: string;
};

const CARDS: SkewCard[] = [
  {
    src: "/img/gallery/g07.webp",
    alt: "Sunlit mountain ridge fading into layered haze",
    label: "Ridgeline",
    tilt: "imgfxSkew__card--left",
  },
  {
    src: "/img/gallery/g19.webp",
    alt: "Quiet coastal shoreline under a wide pale sky",
    label: "Shoreline",
    tilt: "imgfxSkew__card--center",
  },
  {
    src: "/img/gallery/g28.webp",
    alt: "City street at dusk with long warm reflections",
    label: "Dusk street",
    tilt: "imgfxSkew__card--right",
  },
];

export default function ImgfxSkewHover() {
  const [active, setActive] = useState<number | null>(null);

  return (
    <section className="relative w-full overflow-hidden bg-neutral-50 px-6 py-24 dark:bg-neutral-950 sm:px-10">
      <style>{`
        .imgfxSkew__card {
          transform: perspective(1200px) rotateX(0deg) skewY(0deg) translateY(0);
          transition:
            transform 520ms cubic-bezier(0.16, 1, 0.3, 1),
            box-shadow 520ms cubic-bezier(0.16, 1, 0.3, 1),
            filter 520ms cubic-bezier(0.16, 1, 0.3, 1);
          will-change: transform;
        }
        .imgfxSkew__card--left:hover {
          transform: perspective(1200px) rotateX(4deg) skewY(-2.4deg) translateY(-18px) scale(1.015);
        }
        .imgfxSkew__card--center:hover {
          transform: perspective(1200px) rotateX(4deg) skewY(0deg) translateY(-22px) scale(1.02);
        }
        .imgfxSkew__card--right:hover {
          transform: perspective(1200px) rotateX(4deg) skewY(2.4deg) translateY(-18px) scale(1.015);
        }
        .imgfxSkew__img {
          transition: transform 520ms cubic-bezier(0.16, 1, 0.3, 1);
        }
        .imgfxSkew__card:hover .imgfxSkew__img {
          transform: scale(1.08);
        }
        .imgfxSkew__sheen {
          opacity: 0;
          transition: opacity 520ms cubic-bezier(0.16, 1, 0.3, 1);
        }
        .imgfxSkew__card:hover .imgfxSkew__sheen {
          opacity: 1;
        }
        @media (prefers-reduced-motion: reduce) {
          .imgfxSkew__card,
          .imgfxSkew__img,
          .imgfxSkew__sheen {
            transition: none;
          }
          .imgfxSkew__card--left:hover,
          .imgfxSkew__card--center:hover,
          .imgfxSkew__card--right:hover {
            transform: none;
          }
          .imgfxSkew__card:hover .imgfxSkew__img {
            transform: none;
          }
          .imgfxSkew__card:hover .imgfxSkew__sheen {
            opacity: 0;
          }
        }
      `}</style>

      <div className="mx-auto max-w-5xl">
        <div className="mb-14 text-center">
          <p className="text-xs font-semibold uppercase tracking-[0.25em] text-indigo-600 dark:text-indigo-400">
            Image Effect
          </p>
          <h2 className="mt-3 text-3xl font-bold tracking-tight text-neutral-900 dark:text-neutral-50 sm:text-4xl">
            Skew &amp; Lift
          </h2>
          <p className="mt-3 text-sm text-neutral-500 dark:text-neutral-400">
            Hover any frame — it tilts off the page and rises toward you.
          </p>
        </div>

        <div className="grid grid-cols-1 gap-8 sm:grid-cols-3">
          {CARDS.map((card, i) => (
            <figure
              key={card.src}
              onMouseEnter={() => setActive(i)}
              onMouseLeave={() => setActive((cur) => (cur === i ? null : cur))}
              className={`imgfxSkew__card group relative aspect-[3/4] overflow-hidden rounded-2xl bg-neutral-200 shadow-lg shadow-neutral-900/10 ring-1 ring-neutral-900/5 hover:shadow-2xl hover:shadow-indigo-900/25 dark:bg-neutral-800 dark:shadow-black/40 dark:ring-white/10 ${card.tilt}`}
            >
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img
                src={card.src}
                alt={card.alt}
                loading="lazy"
                draggable={false}
                className="imgfxSkew__img h-full w-full object-cover"
              />

              <div
                className="imgfxSkew__sheen pointer-events-none absolute inset-0 bg-gradient-to-tr from-indigo-500/0 via-white/20 to-violet-400/25 mix-blend-overlay"
                aria-hidden="true"
              />

              <figcaption className="pointer-events-none absolute inset-x-0 bottom-0 flex items-center justify-between bg-gradient-to-t from-neutral-950/70 to-transparent px-4 pb-4 pt-10">
                <span className="text-sm font-medium text-white">
                  {card.label}
                </span>
                <span
                  className={`text-[0.65rem] font-semibold uppercase tracking-widest transition-colors duration-300 ${
                    active === i
                      ? "text-indigo-300"
                      : "text-white/50"
                  }`}
                >
                  {active === i ? "Lifted" : "Rest"}
                </span>
              </figcaption>
            </figure>
          ))}
        </div>
      </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 →