Source Code

<div class="ng-stage">
  <p class="ng-hint">Hover any icon — it pulls its nearest neighbors toward itself</p>
  <div class="ng-grid" id="ngGrid">
    <button class="ng-icon" data-label="Home">🏠</button>
    <button class="ng-icon" data-label="Search">🔍</button>
    <button class="ng-icon" data-label="Heart">❤️</button>
    <button class="ng-icon" data-label="Star">⭐</button>
    <button class="ng-icon" data-label="Bell">🔔</button>
    <button class="ng-icon" data-label="Mail">✉️</button>
    <button class="ng-icon" data-label="Camera">📷</button>
    <button class="ng-icon" data-label="Music">🎵</button>
    <button class="ng-icon" data-label="Gift">🎁</button>
    <button class="ng-icon" data-label="Settings">⚙️</button>
  </div>
</div>

Icon Grid Neighbor Pull — Hover Attraction Snippet

Icon Grid Neighbor Pull · Animations · Plain HTML, CSS & JS · Live preview

What's included

Features

Neighbor-to-neighbor attraction — triggered by icon hover, not continuous cursor tracking
getBoundingClientRect gives real center positions, correct even on irregular grids
Linear distance falloff — closer neighbors pull harder, distant ones are unaffected
Unit-vector normalization applies pull magnitude in the exact correct direction
Hovered icon scales up instead of translating, reading clearly as the attraction source
Overshoot cubic-bezier release gives a subtle elastic bounce back to rest
Configurable MAX_REACH and MAX_PULL constants for reach and intensity
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons
Live split-pane editor — preview updates as you type

About this UI Snippet

Icon Grid Neighbor Pull — Distance-Falloff Attraction Between Sibling Icons on Hover

Screenshot of the Icon Grid Neighbor Pull snippet rendered live

This effect makes a grid of icons feel like a connected surface rather than a flat list: hovering one icon doesn't just style itself, it visibly pulls its nearest neighbors a few pixels toward it, with the pull strength fading out for icons farther away. It's a different mechanism from a magnetic grid of dots that follow the live cursor position on every mousemove — here the pull is triggered once per icon-to-icon hover and computed between sibling elements, not between the cursor and a field of points.

Measuring real positions, not grid math

Instead of inferring neighbor distance from row/column indices, the script reads every icon's actual center point with getBoundingClientRect() inside a centers() helper. This matters because it means the effect works correctly even if the grid isn't perfectly uniform — irregular gaps, responsive reflow, or a non-square layout all still produce geometrically correct pull directions and distances.

The falloff formula

For every icon other than the one being hovered, the vector to the hovered icon's center is dx, dy, and dist = Math.sqrt(dx*dx + dy*dy) is the Euclidean distance in pixels. Anything beyond MAX_REACH (170px) doesn't move at all. Within that range, falloff = 1 - dist / MAX_REACH scales linearly from 1 (touching the hovered icon) down to 0 (right at the reach boundary), and the final pull distance is falloff * MAX_PULL.

Applying the pull as a unit vector

ux = dx / dist and uy = dy / dist normalize the direction vector to length 1, so multiplying by pull gives a translation of exactly the right magnitude in exactly the right direction — every affected neighbor slides directly toward the hovered icon's center, not toward some averaged or fixed direction.

The hovered icon itself

The icon under the cursor doesn't translate — translating it would fight with the neighbors moving toward its own position. Instead it scales up slightly (scale(1.12)) to read clearly as the "source" of the attraction, while everything else visibly responds to it.

The spring-like release

On mouseleave, every icon resets to translate(0,0) scale(1). The transition includes cubic-bezier(0.34, 1.56, 0.64, 1) — a curve with a controlled overshoot past 1, so the release has a small bounce rather than a flat linear return, which reinforces the tactile, elastic feel of the whole interaction.

Tuning it

Increase MAX_REACH to make more distant icons react; increase MAX_PULL for a more dramatic pull. For a denser grid, a smaller MAX_REACH keeps the effect localized to true immediate neighbors instead of pulling the whole grid inward on every hover.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't need to re-derive the geometry from scratch. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the pull direction is computed as a normalized unit vector rather than just scaling dx and dy directly, and why MAX_REACH is measured in real pixels from getBoundingClientRect instead of counting grid cells. The same assistant can help optimize it — for instance asking whether recomputing centers() on every mouseenter is wasteful for a very large grid, and whether caching positions and only invalidating on resize would be more efficient. It's also useful for extending the effect: ask it to make the pull also apply a subtle rotation toward the hovered icon, add a repulsion mode where neighbors push away instead of toward, or combine this with the jelly press button's spring physics for an even bouncier neighbor response. 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:

text
Build an "icon grid neighbor pull" hover effect in plain HTML, CSS, and JavaScript with no libraries.

Requirements:
- A grid of icon buttons (at least 8-10) laid out with CSS grid.
- On mouseenter of any one icon, every OTHER icon in the grid must translate a small distance toward the hovered icon's position, with the pull strength falling off linearly based on real measured distance — icons very close to the hovered one move the most, icons beyond a maximum reach distance do not move at all.
- Distances and directions must be computed from actual rendered positions using getBoundingClientRect on every icon (not assumed from row/column index math), so the effect stays geometrically correct even if the grid layout is irregular or reflows responsively.
- The pull direction for each affected neighbor must be a normalized unit vector toward the hovered icon's center, scaled by the falloff-adjusted pull distance, applied via a CSS transform: translate.
- The hovered icon itself should NOT translate toward its own center — give it a distinct visual treatment instead (such as scaling up slightly) so it reads clearly as the source of the attraction.
- On mouseleave of the icon, all icons must animate back to their neutral position and scale using a CSS transition with a slight overshoot easing curve (a cubic-bezier with a value above 1, like cubic-bezier(0.34, 1.56, 0.64, 1)) so the release has a small elastic bounce.
- Expose the maximum reach distance and maximum pull distance as easily tunable constants near the top of the script.

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

  1. 1
    Hover any icon in the gridMove your cursor over one icon and watch its nearest neighbors slide slightly toward it, with farther icons barely moving.
  2. 2
    Move to a different iconMove directly between icons — the pull recalculates on each mouseenter based on the newly hovered icon's position.
  3. 3
    Adjust the reachIn the JS panel, change MAX_REACH to control how far the attraction extends before falling to zero.
  4. 4
    Adjust the pull strengthChange MAX_PULL to control the maximum pixel distance an adjacent neighbor moves.
  5. 5
    Swap in your own iconsReplace the emoji or add SVG icons inside each .ng-icon button — the pull logic reads only positions, not content.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Social/emoji reaction pickers
A reaction or emoji picker grid feels more alive when hovering one option visibly draws its neighbors closer, hinting at a connected, fluid surface.
App launcher and dashboard icon grids
Use on a dashboard shortcut grid or app launcher to add a layer of tactile polish beyond a flat hover color change.
Learn vector normalization and falloff math
A clean, small example of unit-vector direction plus linear distance falloff — patterns reused across particle systems, magnetic effects, and physics UIs.
Onboarding feature icon showcases
Draw attention to a set of feature icons during onboarding — the neighbor pull creates a sense of connectedness between related features.
Social proof / skill icon rows
A row of tech-stack or skill icons on a portfolio site becomes more engaging with this hover behavior than a static row.
Related: Magnetic Grid
See the Magnetic Grid for the cursor-to-field version of an attraction effect, which continuously tracks mousemove instead of triggering per hovered element.

Got questions?

Frequently Asked Questions

Magnetic Grid continuously tracks the live cursor position against a dense field of dots on every mousemove. This snippet instead triggers once per icon on mouseenter/mouseleave and pulls sibling icons toward each other based on their own measured positions — an element-to-element attraction, not a cursor-to-field one.

Every icon's center point is read with getBoundingClientRect() inside the centers() helper. The Euclidean distance between the hovered icon's center and every other icon's center determines whether and how much that icon moves.

If the hovered icon also translated toward its own gravitational center, the motion would look confusing or self-cancelling. Scaling it up instead cleanly signals "this is the source" while every other icon visibly responds by moving toward it.

Yes. Because distances come from actual rendered positions via getBoundingClientRect rather than assumed row/column math, the effect adapts correctly to any grid layout, including irregular gaps or a responsive reflow.

Lower MAX_REACH so it is just larger than the pixel distance between adjacent grid cells — icons further than one or two cells away will then fall outside the falloff range and stay still.

Yes. Click "JSX" for a React component. Keep an array of refs to each icon element, compute positions inside the onMouseEnter handler for a given icon, and apply the resulting transform directly to each ref's style to avoid unnecessary re-renders.