Hover effects are more than just eye candy—they are essential for creating an engaging user experience on modern websites. When implemented thoughtfully, hover effects can provide subtle visual feedback, improve usability, and guide user interaction intuitively.
For e-commerce websites, like the one I was building for Glamour & Gourmandise, the product card is a critical component. It needs to be visually appealing, informative, and highly interactive. To achieve this, I implemented a sophisticated hover effect that reveals additional product details in a sleek and seamless way.
Code
"use client";
import { motion } from "framer-motion"
// Imports...
export default function ProductCard(){
// State
const [isHovered, setIsHovered] = useState(false);
return (
<article
tabIndex={0}
className={cn(
"rounded-special group/product relative flex h-fit cursor-pointer flex-col overflow-hidden",
"aspect-[4/5] border-2 border-dashed border-primary/20 sm:aspect-square md:aspect-[4/5]",
)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* Product Image */}
<Image
src={image}
alt={alt}
className={cn(
"h-auto w-full flex-none object-cover",
"h-[58%] flex-none sm:aspect-auto",
)}
/>
{/* Product Details */}
<div
className={cn(
"group relative h-[42%] w-full grow",
isHovered ? "is-hovered" : "",
)}
>
<motion.div
layout
transition={{
layout: { duration: 0.1 },
// default: { ease: "linear" },
}}
className={cn(
"rounded-special absolute bottom-0 left-0 flex min-h-[105%] w-full flex-col justify-between space-y-2 bg-white p-2 group-[.is-hovered]:h-auto sm:space-y-4 sm:p-3 lg:p-4",
"",
)}
>
<div className="flex-none">
<h3 className="line-clamp-2 font-title font-semibold !leading-[1.1] min-[480px]:text-lg min-[545px]:text-2xl sm:mb-1">
{product.title}
</h3>
<div className="mb-1 hidden h-0 items-center space-x-1 overflow-hidden group-[.is-hovered]:flex group-[.is-hovered]:h-auto sm:mb-3">
{product.tags.map(
({ tag }, i) =>
i < 2 && (
<Badge variant="product-tag" key={i} className="">
{tag}
</Badge>
),
)}
</div>
<p className="line-clamp-3 hidden text-xs leading-[1.1] text-muted-foreground group-[.is-hovered]:line-clamp-3 md:text-sm">
{product.description}
</p>
{product.link && (
<Link
onClick={(e) => e.stopPropagation()}
replace
href={product.link}
className="block w-fit text-xs font-semibold text-primary transition hover:text-secondary group-[.is-hovered]:block sm:hidden"
>
More info
</Link>
)}
</div>
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="">
<p className="font-title text-sm font-semibold leading-none min-[400px]:text-xl">
{product.formattedPrice.priceWithCurrency}
</p>
<span className="hidden text-right text-[0.6rem] leading-none text-secondary min-[545px]:block sm:text-xs">
{product.formattedPrice.priceUnit}
</span>
</div>
{/* Add To Cart Button */}
<Button size="sm" disabled={true}>
<span className="block size-4 animate-spin rounded-full border border-current border-t-transparent" />
</Button>
</div>
</motion.div>
</div>
</article>
);
}
Implementation
The Wrapper
The article
element is the foundation of this hover effect. It is responsible for:
- Maintaining the aspect ratio (responsively adjusted based on screen size).
- Housing all child components, including the image and product details.
- Triggering hover state changes.
- Aspect Ratio: Ensures consistent sizing of the card across different devices.
- Hover State: Sets
isHovered
to true or false to control the animation.
Product Details Section
The product details are housed in an absolutely positioned div
at the bottom of the wrapper. This positioning ensures that the expanding animation occurs within the wrapper, avoiding disruptions to the page layout.
Framer Motion’s layout={true}
is the key to this hover effect. It:
- Animates Height Changes: When the product details expand, layout={true} calculates the difference in height and interpolates the transition smoothly.
- Direction Control: Because the details div is positioned absolutely at the bottom, the expansion happens upward into the card instead of downward.
- User-Friendly Transitions: Without
layout={true}
, the height change would feel abrupt and jarring.
Conclusion
This hover effect showcases how to elevate user experience with smooth animations and thoughtful design. While the actual website for Glamour & Gourmandise opted for a simpler zoom-in effect on images (as per the owner’s preference), this implementation demonstrates the versatility and creative possibilities with Framer Motion and Next.js.
If you’re interested in learning more about layout animations or have tips to optimize this implementation further, let me know!
Author Of article : NathLowe Read full article