Focus Cards

Hover over a card to focus on it, softly blurring the rest of the cards.

Focus Cards

A grid of image cards where hovering over one card brings it into sharp focus while the rest softly blur and scale down.

Installation

npm install next react
pnpm install next react
yarn add next react
bun add next react
npx install next react

Copy the Focus Cards Component

"use client";import React, { useState } from "react";import { cn } from "@/lib/utils";import Image from "next/image";type FocusCard = {  title: string;  src: string;};export const Card = React.memo(  ({    card,    index,    hovered,    setHovered,  }: {    card: FocusCard;    index: number;    hovered: number | null;    setHovered: React.Dispatch<React.SetStateAction<number | null>>;  }) => (    <div      onMouseEnter={() => setHovered(index)}      onMouseLeave={() => setHovered(null)}      className={cn(        "rounded-lg relative bg-secondary overflow-hidden h-60 md:h-96 w-full transition-all duration-300 ease-out",        hovered !== null && hovered !== index && "blur-sm scale-[0.98]",      )}    >      <Image        src={card.src}        alt={card.title}        fill        sizes="(max-width: 768px) 100vw, 33vw"        className="object-cover absolute inset-0"      />      <div        className={cn(          "absolute inset-0 bg-black/50 flex items-end py-8 px-4 transition-opacity duration-300",          hovered === index ? "opacity-100" : "opacity-0",        )}      >        <div className="bg-clip-text text-xl font-medium text-transparent bg-gradient-to-b from-foreground to-muted-foreground md:text-2xl">          {card.title}        </div>      </div>    </div>  ),);Card.displayName = "Card";export function FocusCards({ cards }: { cards: FocusCard[] }) {  const [hovered, setHovered] = useState<number | null>(null);  return (    <div className="grid grid-cols-1 md:grid-cols-3 gap-10 max-w-5xl mx-auto md:px-8 w-full">      {cards.map((card, index) => (        <Card          key={card.title}          card={card}          index={index}          hovered={hovered}          setHovered={setHovered}        />      ))}    </div>  );}export default function FocusCardsDemo() {  const cards: FocusCard[] = [    {      title: "Forest Adventure",      src: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&auto=format&fit=crop&w=600",    },    {      title: "Valley of life",      src: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?q=80&auto=format&fit=crop&w=600",    },    {      title: "Sala behta hi jayega",      src: "https://images.unsplash.com/photo-1470252649378-9c29740c9fa8?q=80&auto=format&fit=crop&w=600",    },    {      title: "Camping is for pros",      src: "https://images.unsplash.com/photo-1504280390367-361c6d9f38f4?q=80&auto=format&fit=crop&w=600",    },    {      title: "The road not taken",      src: "https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?q=80&auto=format&fit=crop&w=600",    },    {      title: "The First Rule",      src: "https://images.unsplash.com/photo-1511497584788-876760111969?q=80&auto=format&fit=crop&w=600",    },  ];  return <FocusCards cards={cards} />;}

Usage

FocusCards renders an array of cards. Each Card object contains a title and an src (image URL).

"use client";

import { FocusCards } from "@/components/mvpblocks/cards/focus-cards/focus-cards";

const cards = [
  {
    title: "Forest Adventure",
    src: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&auto=format&fit=crop&w=600",
  },
  {
    title: "Valley of life",
    src: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?q=80&auto=format&fit=crop&w=600",
  },
  {
    title: "Sala behta hi jayega",
    src: "https://images.unsplash.com/photo-1470252649378-9c29740c9fa8?q=80&auto=format&fit=crop&w=600",
  },
  {
    title: "Camping is for pros",
    src: "https://images.unsplash.com/photo-1504280390367-361c6d9f38f4?q=80&auto=format&fit=crop&w=600",
  },
  {
    title: "The road not taken",
    src: "https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?q=80&auto=format&fit=crop&w=600",
  },
  {
    title: "The First Rule",
    src: "https://images.unsplash.com/photo-1511497584788-876760111969?q=80&auto=format&fit=crop&w=600",
  },
];

export default function MyComponent() {
  return <FocusCards cards={cards} />;
}

Props

ComponentPropTypeDescription
CardcardFocusCardThe card object containing title and src properties
CardindexnumberThe index of the card in the array
Cardhoverednumber | nullThe index of the currently hovered card, or null if none is hovered
CardsetHoveredReact.Dispatch<React.SetStateAction<number | null>>Function to update the hovered state
FocusCardscardsFocusCard[]An array of card objects to render

FocusCard

PropTypeDescription
titlestringThe title shown on hover at the bottom of the card
srcstringThe image URL used as the card background

Features

  • Focus on Hover: The hovered card stays sharp while siblings receive blur-sm and a subtle scale-[0.98]
  • Smooth Transitions: All state changes animate with a 300ms ease-out transition
  • Revealed Title: The title fades in with a gradient when its card is hovered
  • Responsive: Single column on mobile, three columns on medium screens and up
  • Themed: Card surfaces use bg-secondary and titles use the foreground/muted gradient

Note

The Card component is wrapped in React.memo so untouched cards skip re-renders when the hovered index changes.

Last updated on