001COMPONENTSPreview

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

Preview
001
pointer / proximitymove to test
COMPONENTS / React

Move 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

01

Create the grid contract

Start with a fixed cell system and a clear pointer coordinate model.
02

Measure proximity

Compare the pointer position with each cell center and clamp the influence.
03

Turn distance into feedback

Use color, shadow, and scale as restrained output signals.
tsx
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.

jump to code