Cursor Grid
A pointer-led grid study about proximity, response, and the quiet choreography between a cursor and a layout.
- Difficulty
- Intermediate
- Build time
- 2–3 hours
- Published
- Jul 27, 2026
- Updated
- Jul 27, 2026
Interactive preview shell
PreviewMove your pointer over the grid to test proximity feedback.
01 / Overview
A pointer-led grid that explains proximity.
Cursor Grid turns pointer position into a readable field of response. The useful part is the small distance calculation behind the visual treatment.
02 / What you will learn
The idea behind the interaction.
- Map pointer coordinates into a stable local space.
- Calculate proximity without a heavy animation loop.
- Keep the interaction legible with hover, keyboard, and reduced-motion fallbacks.
03 / How it works
Structure first. Tuning second.
The grid normalizes the pointer against its own bounds, measures distance from each cell, and uses that value to tune the cell’s visual response.
04 / Build sequence
Step by step
Create the grid contract
Measure proximity
Turn distance into feedback
import { useState } from "react";
const GRID_SIZE = 5;
export function CursorGrid() {
const [pointer, setPointer] = useState({ x: 0.5, y: 0.5, active: false });
function trackPointer(event: React.PointerEvent<HTMLDivElement>) {
const bounds = event.currentTarget.getBoundingClientRect();
setPointer({
x: (event.clientX - bounds.left) / bounds.width,
y: (event.clientY - bounds.top) / bounds.height,
active: true,
});
}
return (
<div onPointerMove={trackPointer} onPointerLeave={() => setPointer((state) => ({ ...state, active: false }))}>
{Array.from({ length: GRID_SIZE * GRID_SIZE }, (_, index) => {
const column = index % GRID_SIZE;
const row = Math.floor(index / GRID_SIZE);
const distance = Math.hypot(pointer.x - (column + 0.5) / GRID_SIZE, pointer.y - (row + 0.5) / GRID_SIZE);
const proximity = pointer.active ? Math.max(0, 1 - distance * 3.2) : 0;
return <span key={index} style={{ opacity: 0.2 + proximity * 0.8 }} />;
})}
</div>
);
}
/* CSS */
.marquee-track {
display: flex;
width: max-content;
animation: marquee 16s linear infinite;
}
@keyframes marquee {
to { transform: translateX(-50%); }
}05 / Notes
Production details belong in the experiment.
06 / Props & API
A stable surface for the future build.
- reducedMotion
- boolean
- Keeps the preview readable without nonessential movement.
- className
- string
- Allows the experiment shell to inherit the MADLAB layout.
- status
- MadlabStatus
- Communicates whether the experiment is draft, preview, or published.
07 / Performance
Measure before adding complexity.
Keep animation work local to the interaction, avoid unnecessary state updates, and pause nonessential work when the preview is not visible.
08 / Accessibility
The visual idea still needs a clear interface.
Every future experiment should have a semantic name, keyboard path, reduced-motion fallback, usable contrast, and a text explanation that does not depend on the animation.
09 / Mobile
A considered small screen composition.
On narrow screens, the preview should simplify its visual workload, preserve the primary idea, and never require horizontal scrolling to understand the experiment.
Source actions
Final source actions
Source is available in the implementation block above.
Continue exploring