An interactive grid component that creates smooth hover animations with gradient highlights following mouse movement. Perfect for showcasing skills, technologies, or any categorized content with engaging visual feedback.
( REACTJS )
( NEXTJS )
( JAVASCRIPT )
( TYPESCRIPT )
( GSAP )
( MOTION.DEV )
( TAILWIND )
( REACTJS )
( NEXTJS )
( JAVASCRIPT )
( TYPESCRIPT )
( GSAP )
( MOTION.DEV )
( TAILWIND )
npm i motionHoverGrid.tsx
"use client";
import { useState } from "react";
import { motion } from "motion/react";
interface HoverGridProps {
items?: string[];
highlightGradients?: string[];
}
export default function HoverGrid({
items = [
"( HTML )",
"( CSS )",
"( JAVASCRIPT )",
"( GSAP )",
"( SCROLLTRIGGER )",
"( REACT )",
"( THREE.JS )",
],
highlightGradients = [
"linear-gradient(135deg, #E24E1B, #F6A623)",
"linear-gradient(135deg, #4381C1, #5BB5F0)",
"linear-gradient(135deg, #F79824, #FFD54F)",
"linear-gradient(135deg, #04A777, #4DD599)",
"linear-gradient(135deg, #5B8C5A, #A8E6CF)",
"linear-gradient(135deg, #2176FF, #33A1FD)",
"linear-gradient(135deg, #818D92, #B0BEC5)",
],
}: HoverGridProps) {
const [highlightStyle, setHighlightStyle] = useState({
x: 0,
y: 0,
width: 0,
height: 0,
gradient: highlightGradients[0],
});
const handleHover = (
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
index: number
) => {
const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
setHighlightStyle({
x:
rect.left -
(e.currentTarget.offsetParent as HTMLElement).getBoundingClientRect()
.left,
y:
rect.top -
(e.currentTarget.offsetParent as HTMLElement).getBoundingClientRect()
.top,
width: rect.width,
height: rect.height,
gradient: highlightGradients[index % highlightGradients.length],
});
};
return (
<div className="w-full min-h-screen bg-neutral-900 flex items-center justify-center px-4 py-10">
{/* Desktop & Tablet */}
<div className="hidden sm:block relative w-4/5 border border-white/20 overflow-hidden rounded-xl">
<motion.div
className="absolute pointer-events-none z-0 "
style={{ background: highlightStyle.gradient }}
animate={{
x: highlightStyle.x,
y: highlightStyle.y,
width: highlightStyle.width,
height: highlightStyle.height,
}}
transition={{ type: "spring", stiffness: 250, damping: 25 }}
/>
{/* Row 1 */}
<div className="flex border-b border-white/20">
{items.slice(0, 3).map((label, idx) => (
<div
key={label}
className="flex-1 flex items-center justify-center border-r last:border-r-0 border-white/20 cursor-pointer relative z-10 py-20"
onMouseEnter={(e) => handleHover(e, idx)}
>
<p className="uppercase text-white text-sm font-medium">
{label}
</p>
</div>
))}
</div>
{/* Row 2 */}
<div className="flex">
{items.slice(3).map((label, idx) => (
<div
key={label}
className="flex-1 flex items-center justify-center border-r last:border-r-0 border-white/20 cursor-pointer relative z-10 py-20 border-t"
onMouseEnter={(e) => handleHover(e, idx + 3)}
>
<p className="uppercase text-white text-sm font-medium">
{label}
</p>
</div>
))}
</div>
</div>
{/* Mobile */}
<div className="grid grid-cols-2 sm:hidden gap-4 w-full max-w-md">
{items.map((label, idx) => (
<div
key={idx}
className="bg-neutral-800 text-white py-10 flex items-center justify-center"
>
<p className="uppercase text-sm font-medium">{label}</p>
</div>
))}
</div>
</div>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
items | string[] | Tech stack items | Array of text items to display in the grid |
highlightGradients | string[] | Default gradients | Array of CSS gradient strings for hover highlights |
Grid Layout: The component automatically arranges items in a 2-row layout:
Animation Settings:
// Modify the spring animation
transition={{
type: "spring",
stiffness: 250, // Higher = snappier
damping: 25 // Higher = less bouncy
}}