Web InnoventixFreeCode

Overlay Title Effect

Original · free

A dark gradient overlay with a title fades in over the image on hover.

byWeb InnoventixReact + Tailwind
overlaytitleimageeffect
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-overlay-title.json
imgfx-overlay-title.tsx
"use client";

import { useRef } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";

type OverlayCard = {
  src: string;
  alt: string;
  kicker: string;
  title: string;
  blurb: string;
};

const CARDS: OverlayCard[] = [
  {
    src: "/img/gallery/g07.webp",
    alt: "Morning light spilling across a quiet mountain ridge",
    kicker: "Field Notes",
    title: "Above the Timberline",
    blurb: "Six hours of climbing for ninety seconds of gold.",
  },
  {
    src: "/img/gallery/g19.webp",
    alt: "Rain-slicked city street glowing under neon signage at night",
    kicker: "After Dark",
    title: "Neon & Rain",
    blurb: "The city looks best when the pavement holds the light.",
  },
  {
    src: "/img/gallery/g28.webp",
    alt: "Calm coastline where pale sand meets turquoise shallows",
    kicker: "Coastlines",
    title: "The Slow Tide",
    blurb: "Where the map runs out and the horizon takes over.",
  },
];

export default function ImgfxOverlayTitle() {
  const sectionRef = useRef<HTMLDivElement>(null);
  const inView = useInView(sectionRef, { once: true, margin: "-80px" });
  const reduceMotion = useReducedMotion();

  return (
    <section className="relative w-full bg-neutral-50 py-20 dark:bg-neutral-950 sm:py-28">
      <style>{`
        .imgfxOverlayTitle-media {
          transition: transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
          will-change: transform;
        }
        .imgfxOverlayTitle-card:hover .imgfxOverlayTitle-media,
        .imgfxOverlayTitle-card:focus-within .imgfxOverlayTitle-media {
          transform: scale(1.06);
        }
        .imgfxOverlayTitle-scrim {
          opacity: 0;
          transition: opacity 550ms cubic-bezier(0.4, 0, 0.2, 1);
        }
        .imgfxOverlayTitle-card:hover .imgfxOverlayTitle-scrim,
        .imgfxOverlayTitle-card:focus-within .imgfxOverlayTitle-scrim {
          opacity: 1;
        }
        .imgfxOverlayTitle-reveal {
          opacity: 0;
          transform: translateY(14px);
          transition: opacity 500ms cubic-bezier(0.22, 1, 0.36, 1),
            transform 500ms cubic-bezier(0.22, 1, 0.36, 1);
        }
        .imgfxOverlayTitle-card:hover .imgfxOverlayTitle-reveal,
        .imgfxOverlayTitle-card:focus-within .imgfxOverlayTitle-reveal {
          opacity: 1;
          transform: translateY(0);
        }
        .imgfxOverlayTitle-reveal.imgfxOverlayTitle-delay {
          transition-delay: 70ms;
        }
        @media (prefers-reduced-motion: reduce) {
          .imgfxOverlayTitle-media,
          .imgfxOverlayTitle-scrim,
          .imgfxOverlayTitle-reveal {
            transition: none;
          }
          .imgfxOverlayTitle-card:hover .imgfxOverlayTitle-media,
          .imgfxOverlayTitle-card:focus-within .imgfxOverlayTitle-media {
            transform: none;
          }
        }
      `}</style>

      <div className="mx-auto w-full max-w-6xl px-6">
        <div className="mx-auto max-w-2xl text-center">
          <p className="text-xs font-semibold uppercase tracking-[0.2em] text-indigo-600 dark:text-indigo-400">
            Image Effect
          </p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-neutral-900 dark:text-neutral-50 sm:text-4xl">
            Overlay Title Reveal
          </h2>
          <p className="mt-4 text-base leading-relaxed text-neutral-600 dark:text-neutral-400">
            A dark gradient washes over the photo and the title rises into view.
            <span className="ml-1 font-medium text-neutral-800 dark:text-neutral-200">
              Hover, or focus with the keyboard, to reveal.
            </span>
          </p>
        </div>

        <div
          ref={sectionRef}
          className="mt-14 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"
        >
          {CARDS.map((card, i) => (
            <motion.article
              key={card.src}
              initial={
                reduceMotion ? false : { opacity: 0, y: 24 }
              }
              animate={
                reduceMotion || inView
                  ? { opacity: 1, y: 0 }
                  : undefined
              }
              transition={{
                duration: 0.5,
                delay: reduceMotion ? 0 : i * 0.09,
                ease: [0.22, 1, 0.36, 1],
              }}
              className="imgfxOverlayTitle-card group relative block overflow-hidden rounded-2xl bg-neutral-200 shadow-sm outline-none ring-1 ring-black/5 focus-within:ring-2 focus-within:ring-indigo-500 dark:bg-neutral-900 dark:ring-white/10"
            >
              <a
                href="#"
                onClick={(e) => e.preventDefault()}
                aria-label={`${card.title} — ${card.blurb}`}
                className="block outline-none"
              >
                <div className="relative aspect-[4/5] w-full overflow-hidden">
                  {/* eslint-disable-next-line @next/next/no-img-element */}
                  <img
                    src={card.src}
                    alt={card.alt}
                    loading="lazy"
                    draggable={false}
                    className="imgfxOverlayTitle-media absolute inset-0 h-full w-full object-cover"
                  />

                  <div className="imgfxOverlayTitle-scrim absolute inset-0 bg-gradient-to-t from-neutral-950/90 via-neutral-950/45 to-transparent" />

                  <div className="absolute inset-x-0 bottom-0 p-5">
                    <p className="imgfxOverlayTitle-reveal text-[0.7rem] font-semibold uppercase tracking-[0.2em] text-indigo-300">
                      {card.kicker}
                    </p>
                    <h3 className="imgfxOverlayTitle-reveal imgfxOverlayTitle-delay mt-1.5 text-xl font-semibold leading-tight text-white">
                      {card.title}
                    </h3>
                    <p className="imgfxOverlayTitle-reveal imgfxOverlayTitle-delay mt-1 text-sm leading-snug text-neutral-200">
                      {card.blurb}
                    </p>
                  </div>
                </div>
              </a>
            </motion.article>
          ))}
        </div>
      </div>
    </section>
  );
}

Dependencies

motion

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 →