Bento Grid

Asymmetric responsive grid with hover-expand animation and spotlight glow

layoutgridbentoresponsiveinteractive
Install dependencies
$npm install framer-motion
Preview

Source Code

"use client";

import { useRef, useState } from "react";
import { motion } from "framer-motion";

interface BentoGridProps {
  children: React.ReactNode;
  columns?: number;
  className?: string;
}

interface BentoGridItemProps {
  children: React.ReactNode;
  colSpan?: number;
  rowSpan?: number;
  className?: string;
}

export function BentoGrid({
  children,
  columns = 3,
  className = "",
}: BentoGridProps) {
  return (
    <div
      className={`grid gap-4 ${className}`}
      style={{
        gridTemplateColumns: `repeat(${columns}, 1fr)`,
        gridAutoRows: "minmax(140px, auto)",
      }}
    >
      {children}
    </div>
  );
}

export function BentoGridItem({
  children,
  colSpan = 1,
  rowSpan = 1,
  className = "",
}: BentoGridItemProps) {
  const ref = useRef<HTMLDivElement>(null);
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
  const [isHovered, setIsHovered] = useState(false);

  const handleMouseMove = (e: React.MouseEvent) => {
    if (!ref.current) return;
    const rect = ref.current.getBoundingClientRect();
    setMousePos({
      x: e.clientX - rect.left,
      y: e.clientY - rect.top,
    });
  };

  return (
    <motion.div
      ref={ref}
      onMouseMove={handleMouseMove}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      whileHover={{ scale: 1.02 }}
      transition={{ type: "spring", stiffness: 300, damping: 20 }}
      className={`relative overflow-hidden rounded-2xl border border-white/15 bg-gray-900 p-6 ${className}`}
      style={{
        gridColumn: `span ${colSpan}`,
        gridRow: `span ${rowSpan}`,
      }}
    >
      {/* Spotlight glow */}
      <div
        className="pointer-events-none absolute -inset-px rounded-2xl transition-opacity duration-300"
        style={{
          opacity: isHovered ? 1 : 0,
          background: `radial-gradient(400px circle at ${mousePos.x}px ${mousePos.y}px, rgba(56,189,248,0.12), transparent 40%)`,
        }}
      />
      <div className="relative z-10">{children}</div>
    </motion.div>
  );
}

export default BentoGrid;