Web InnoventixFreeCode

Grayscale Color Effect

Original · free

Greyscale image turns to full colour on hover.

byWeb InnoventixReact + Tailwind
grayscalecolorimageeffect
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-grayscale-color.json
imgfx-grayscale-color.tsx
"use client";

export default function ImgfxGrayscaleColor() {
  const photos = [
    {
      src: "/img/gallery/g07.webp",
      alt: "Sunlit mountain ridge fading into layered morning haze",
      caption: "Ridgeline, first light",
    },
    {
      src: "/img/gallery/g14.webp",
      alt: "Quiet city street with wet pavement reflecting neon signs",
      caption: "After the rain",
    },
    {
      src: "/img/gallery/g23.webp",
      alt: "Close portrait of a woman turning toward soft window light",
      caption: "By the window",
    },
    {
      src: "/img/gallery/g31.webp",
      alt: "Bowl of fresh market produce in bright saturated tones",
      caption: "Market morning",
    },
  ];

  return (
    <section className="relative w-full bg-white px-6 py-20 sm:py-28 dark:bg-neutral-950">
      <style>{`
        .imgfx-gc-swatch {
          filter: grayscale(1) contrast(1.02) brightness(0.98);
          transition:
            filter 640ms cubic-bezier(0.22, 1, 0.36, 1),
            transform 640ms cubic-bezier(0.22, 1, 0.36, 1);
          will-change: filter, transform;
        }
        .imgfx-gc-card:hover .imgfx-gc-swatch,
        .imgfx-gc-card:focus-within .imgfx-gc-swatch {
          filter: grayscale(0) contrast(1) brightness(1) saturate(1.06);
          transform: scale(1.04);
        }
        .imgfx-gc-dot {
          transition: background-color 640ms cubic-bezier(0.22, 1, 0.36, 1);
        }
        .imgfx-gc-card:hover .imgfx-gc-dot,
        .imgfx-gc-card:focus-within .imgfx-gc-dot {
          background-color: currentColor;
        }
        @media (prefers-reduced-motion: reduce) {
          .imgfx-gc-swatch,
          .imgfx-gc-dot {
            transition: none;
          }
          .imgfx-gc-card:hover .imgfx-gc-swatch,
          .imgfx-gc-card:focus-within .imgfx-gc-swatch {
            transform: none;
          }
        }
      `}</style>

      <div className="mx-auto max-w-5xl">
        <div className="mb-12 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 sm:text-4xl dark:text-neutral-50">
            Greyscale to colour
          </h2>
          <p className="mx-auto mt-4 max-w-xl text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
            Every frame rests in monochrome until you reach it. Hover or tab to a
            card and the colour floods back in one smooth sweep.
          </p>
          <p className="mt-5 inline-flex items-center gap-2 rounded-full border border-neutral-200 bg-neutral-50 px-3 py-1 text-xs font-medium text-neutral-500 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-400">
            <span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
            Hover to reveal the colour
          </p>
        </div>

        <div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
          {photos.map((photo) => (
            <figure
              key={photo.src}
              tabIndex={0}
              className="imgfx-gc-card group relative overflow-hidden rounded-2xl bg-neutral-100 text-indigo-500 shadow-sm outline-none ring-1 ring-neutral-200 transition-shadow duration-300 hover:shadow-xl focus-visible:ring-2 focus-visible:ring-indigo-500 dark:bg-neutral-900 dark:text-indigo-400 dark:ring-neutral-800"
            >
              <div className="aspect-[4/3] w-full overflow-hidden">
                {/* eslint-disable-next-line @next/next/no-img-element */}
                <img
                  src={photo.src}
                  alt={photo.alt}
                  loading="lazy"
                  draggable={false}
                  className="imgfx-gc-swatch h-full w-full object-cover"
                />
              </div>
              <figcaption className="pointer-events-none absolute inset-x-0 bottom-0 flex items-center gap-2.5 bg-gradient-to-t from-black/70 via-black/25 to-transparent px-4 pb-4 pt-10 text-sm font-medium text-white">
                <span className="imgfx-gc-dot h-2 w-2 shrink-0 rounded-full bg-white/50" />
                {photo.caption}
              </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 →