You Might Also Like
Hover Reveal List — Free HTML CSS JS Preview Snippet
Hover Reveal List · Navigation · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Hover Reveal List — Floating Thumbnail That Follows the Cursor

The hover reveal list is the editorial index — common on portfolio and studio sites — where a clean list of project names sits quietly until you hover a row, at which point a thumbnail preview fades in and floats along with your cursor, tilted slightly like a card held in the air. This snippet builds it with plain HTML, CSS, and a small vanilla JavaScript loop.
A single shared preview
Rather than one image per row, there's a single fixed-position .hr-preview element that all rows share. Hovering a row sets the preview's background (from the row's data-img) and reveals it; leaving hides it. Reusing one element keeps the DOM minimal and means the preview can smoothly cross-fade as you move from one row to the next instead of having images pop in and out. In production you'd set the background to a real thumbnail URL per row.
Easing toward the cursor with lerp
The preview doesn't snap to the pointer — it trails. A pointermove handler stores the raw cursor position as a target, and an animation loop eases the preview's actual position toward it with linear interpolation: current += (target - current) * 0.18 each frame. That lag is what makes the thumbnail feel like it's floating and being dragged through the air behind the cursor, rather than rigidly pinned to it. The 0.18 factor tunes how tightly it follows.
A self-terminating loop
The requestAnimationFrame loop only runs while it has work to do. It keeps going while the preview is visible or while it's still catching up to the cursor (the distance check), and sets raf = null to stop once the preview is hidden and settled. The next pointermove restarts it. This avoids burning a frame loop forever when the pointer is idle and nothing is shown.
The reveal and tilt
The preview starts hidden at opacity: 0, scaled down and rotated -6deg. Adding the .show class transitions it to full opacity, full scale, and -4deg — so it both fades in and subtly straightens, giving a lively pop. The slight constant rotation makes it read as a physical card rather than a flat rectangle. A strong drop shadow lifts it above the list.
The list interaction
Each row's name is muted at rest and brightens and nudges right on hover, while its meta text lightens — so the row itself responds, not just the preview. Rows sit above the preview in z-index so the floating image passes behind the text, keeping the names readable. The whole thing reads as a refined, interactive index.
Customizing it
Swap each row's data-img for real thumbnail URLs, change the preview size and tilt, tune the 0.18 follow tightness, or adjust the row hover treatment. Make rows real links to project pages. Pair it with a flip link navigation or a hero parallax grid for a complete studio site.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to trace the interpolation loop by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly what the lerp formula current += (target - current) * 0.18 does frame by frame, or why the requestAnimationFrame loop deliberately stops itself once the preview is hidden and has caught up to the cursor. The same assistant is useful for optimizing it — ask whether the distance check (Math.abs comparisons) used to decide when to stop the loop could ever leave it running forever under specific mouse-movement patterns, and how to guard against that. It's just as handy for extending the effect: ask it to add a second, slower-trailing preview layer for a parallax depth effect, make the preview's tilt angle respond to the direction of cursor movement instead of a fixed value, or crossfade between two different thumbnails when moving quickly between rows. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an editorial hover-reveal project list in plain HTML, CSS, and JavaScript where hovering a row floats a single shared thumbnail preview that trails the cursor — no library, no per-row image elements.
Requirements:
- A list of rows, each containing a project name and metadata, with each row storing its preview image or gradient reference in a data attribute.
- One single fixed-position preview element shared across all rows (not one per row), initially hidden with reduced opacity, scaled down, and rotated slightly.
- On pointerenter of a row, set the shared preview's background from that row's data attribute and reveal it; on pointerleave, hide it again — so moving between rows cross-fades the same element rather than swapping distinct images.
- Track the raw pointer position on every pointermove over the list's container, storing it as a target coordinate separate from the preview's current displayed coordinate.
- Run a requestAnimationFrame loop that each frame moves the current displayed coordinate a fraction of the way toward the target coordinate (linear interpolation, not a direct snap), so the preview visibly lags behind and trails the cursor smoothly, and apply that current coordinate to the preview's position every frame.
- The loop must not run indefinitely: it should keep going only while the preview is currently visible or while the displayed coordinate is still measurably far from the target, and stop itself (clearing its handle) once both conditions are false, restarting cleanly on the next pointer movement.
- Give the row names a hover state that brightens their color and nudges them horizontally, while ensuring the row text always renders above the floating preview in stacking order so it stays readable.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA quiet list of project names renders.
- 2Hover a rowA thumbnail preview fades in near your cursor.
- 3Move the cursorThe preview eases along, trailing and tilted.
- 4Move between rowsThe preview cross-fades to each project's image.
- 5Swap in thumbnailsSet each row's data-img to a real image URL.
- 6Tune the followAdjust the lerp factor and preview tilt.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
A single fixed preview element keeps the DOM minimal and lets the thumbnail cross-fade as you move between rows, rather than separate images popping in and out. Each row just sets the shared preview's background from its data-img and toggles its visibility, so moving down the list smoothly swaps the picture.
A pointermove handler stores the raw cursor position as a target, and an animation loop eases the preview toward it with linear interpolation: current += (target - current) * 0.18 each frame. That deliberate lag makes the thumbnail feel like it's floating and being dragged through the air, rather than rigidly pinned to the pointer. The 0.18 factor controls how tightly it follows.
No. The requestAnimationFrame loop only runs while the preview is visible or still catching up to the cursor; once it's hidden and settled, it sets the frame handle to null and stops. The next pointermove restarts it. This avoids running a frame loop forever while the pointer is idle and nothing is shown.
The rows sit above the preview in z-index, so the thumbnail passes behind the text as it follows the cursor. The names stay fully readable, and the preview reads as a layer floating between the list and the background rather than obscuring the content.
Render the rows from data and keep the cursor target and eased position in refs. Run the lerp loop in a mount effect, starting it on pointermove and cancelling on unmount. Set the shared preview's background and visibility from the hovered row's state. The CSS ports directly; in Tailwind, position the preview fixed with transform utilities and drive its background via an inline variable.