Directional Hover Card
A picture or content card that detects the exact edge (top, right, bottom, left) the mouse cursor enters from, and slides an information overlay in from that specific direction. It exits out the same edge the mouse leaves.
Use
Rich portfolio image galleries
Avoid
Text-heavy informative tables
Safer Alternative
No safer default listed.
Risk Level
Low risk
Live Demo
Project Apollo
A deep dive into spatial UI elements and directional awareness.
left edge detection
What It Is
A picture or content card that detects the exact edge (top, right, bottom, left) the mouse cursor enters from, and slides an information overlay in from that specific direction. It exits out the same edge the mouse leaves.
✓When to Use
- Rich portfolio image galleries
- E-commerce product grids
✕When NOT to Use
- Text-heavy informative tables
- Mobile-first designs lacking hover states
Configuration Tips
- 01Calculate the angle of the mouse entry relative to the center of the element to determine which of the 4 quadrants the mouse crossed.
You've Seen It In
AI Implementation Prompts
Move from a fast scaffold to production details, then tune timing and edge states.
Create a card that detects mouse entry direction using trigonometry and slides an overlay div in from that corresponding side.
Reference Implementation
The same implementation approach used by the live preview, kept compact enough to inspect and adapt.
"use client";
import { motion, AnimatePresence } from 'framer-motion';
import { useState, MouseEvent as ReactMouseEvent, useEffect } from 'react';
type Direction = 'top' | 'right' | 'bottom' | 'left';
export function DirectionalHoverCardDemo({ isPlaying = false }: { isPlaying?: boolean }) {
const [direction, setDirection] = useState<Direction>('left');
const [isHovered, setIsHovered] = useState(false);
const [autoPlayPhase, setAutoPlayPhase] = useState(0);
const getDirection = (e: ReactMouseEvent<HTMLDivElement, globalThis.MouseEvent>) => {
const { width, height, left, top } = e.currentTarget.getBoundingClientRect();
"text-stone-400 italic">// Calculate center
const cx = left + width / 2;
const cy = top + height / 2;
"text-stone-400 italic">// Calculate relative coordinates and normalize
const x = (e.clientX - cx) * (height / width);
const y = (e.clientY - cy);
"text-stone-400 italic">// Determine angle
const angle = Math.atan2(y, x) * (180 / Math.PI);
if (angle >= -45 && angle < 45) return 'right';
if (angle >= 45 && angle < 135) return 'bottom';
if (angle >= -135 && angle < -45) return 'top';
return 'left';
};
const handleMouseEnter = (e: ReactMouseEvent<HTMLDivElement>) => {
setIsHovered(true);
setDirection(getDirection(e));
};
const handleMouseLeave = (e: ReactMouseEvent<HTMLDivElement>) => {
setIsHovered(false);
setDirection(getDirection(e));
};
"text-stone-400 italic">// Auto play logic for grid view
useEffect(() => {
if (!isPlaying || isHovered) return;
const autoDirs: Direction[] = ['left', 'top', 'right', 'bottom'];
const loop = setInterval(() => {
setAutoPlayPhase(p => (p + 1) % 8);
}, 1500);
return () => clearInterval(loop);
}, [isPlaying, isHovered]);
const activeHover = isHovered || (isPlaying && autoPlayPhase % 2 === 0);
const activeDir = isHovered ? direction : (['left', 'top', 'right', 'bottom'] as Direction[])[Math.floor(autoPlayPhase / 2) % 4];
const variants = {
initial: (dir: Direction) => {
switch (dir) {
case 'top': return { y: "-100%", x: 0 };
case 'right': return { x: "100%", y: 0 };
case 'bottom': return { y: "100%", x: 0 };
case 'left': return { x: "-100%", y: 0 };
}
},
animate: { x: 0, y: 0 },
exit: (dir: Direction) => {
switch (dir) {
case 'top': return { y: "-100%", x: 0 };
case 'right': return { x: "100%", y: 0 };
case 'bottom': return { y: "100%", x: 0 };
case 'left': return { x: "-100%", y: 0 };
}
}
};
return (
<div className="flex h-64 w-full items-center justify-center overflow-hidden rounded-2xl bg-stone-100 p-4 shadow-sm ring-1 ring-stone-200">
<div
className="relative w-64 h-40 bg-white rounded-xl shadow-md overflow-hidden border border-stone-200"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{"text-stone-400 italic">/* Background Base (e.g. Image or Pattern) */}
<div className="absolute inset-0 flex flex-col items-center justify-center p-6 bg-gradient-to-br from-stone-50 to-stone-200">
<svg className="w-12 h-12 text-stone-300 mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span className="text-stone-400 font-medium text-sm">Portfolio Image</span>
</div>
{"text-stone-400 italic">/* Overlay Panel */}
<AnimatePresence custom={activeDir} initial={false}>
{activeHover && (
<motion.div
key="overlay"
custom={activeDir}
variants={variants}
initial="initial"
animate="animate"
exit="exit"
transition={{ type: "tween", ease: "easeInOut", duration: 0.3 }}
className="absolute inset-0 bg-indigo-600/95 backdrop-blur-sm p-6 flex flex-col justify-end text-white"
>
<h3 className="font-bold text-lg leading-tight mb-1">Project Apollo</h3>
<p className="text-xs text-indigo-200 line-clamp-2">A deep dive into spatial UI elements and directional awareness.</p>
<div className="mt-4 flex items-center justify-between">
<span className="text-xs font-bold uppercase tracking-wider">View Case</span>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
{"text-stone-400 italic">/* Context Label for Demo visually */}
<p className="absolute bottom-6 text-[10px] text-stone-400 font-bold uppercase tracking-[0.2em]">{activeDir} edge detection</p>
</div>
);
}Related Effects
Hover Lift Effect
A card or button smoothly translates upwards by a few pixels and increases its shadow cast, creating the illusion of moving closer to the user on hover.
Slide Transition
A view or component enters the screen by sliding linearly from an edge, providing spatial context about where the user is navigating to.