Install
npm i near-fieldThe core is about 1.5 KB min+gzip with zero dependencies, and works with React 18 and 19. Without React, use near-field/vanilla.
No bundler
The build uses only relative imports, so plain HTML can load the vanilla adapter straight from a CDN.
<script type="module">
import { glow } from
"https://cdn.jsdelivr.net/npm/near-field@0.2/dist/vanilla.js";
glow(document.querySelector(".card"));
</script>Quick start
Attach the ref, then read the properties in your CSS.
import { useNearField } from "near-field";
function Card() {
const ref = useNearField({ radius: 140 });
return <div ref={ref} className="card" />;
}
/* --nf eased 0..1, --nf-x / --nf-y unit vector */
.card {
transform: translate(
calc(var(--nf-x) * var(--nf) * 8px),
calc(var(--nf-y) * var(--nf) * 8px)
);
}Values are written straight to the element’s style, so nothing re-renders.
Properties
What every registered element carries.
--nf0 to 1- How close the pointer is, eased. 0 outside the radius, 1 on the element.
--nf-x−1 to 1- Horizontal part of the unit vector from the element toward the pointer.
--nf-y−1 to 1- Vertical part of the same vector.
--nf is 0 outside the radius, and all three reset when the pointer leaves the page.
How it’s measured.
Distance to the element’s box, not its centre, eased with smoothstep. Move toward the box to check the numbers.
- t = 1 − min(d / r, 1)d is the pointer’s distance to the box, zero inside it. r is the radius, 140 px unless you set one.(1)
- --nf = t² (3 − 2t)Smoothstep, so the pull fades in and out instead of starting with a jolt.(2)
- (--nf-x, --nf-y) = (p − c) / |p − c|The unit vector from the element’s centre c toward the pointer p.(3)
useNearField
The primitive. Returns a callback ref; attach it to any element.
const ref = useNearField(options?);Options
radiusnumber = 140- Distance in px from the element's box at which proximity begins.
onFrame(frame) => void- Called on each field update with the raw frame, for canvas, WebGL, or anything CSS can't reach.
cssVarsboolean = true- Write --nf, --nf-x and --nf-y on the element. Turn off when you only need onFrame.
disabledboolean = false- Pause the field for this element and clear its properties.
The frame
onFrame receives the raw numbers behind the properties, for when CSS isn’t the target.
const ref = useNearField({
radius: 200,
onFrame: ({ proximity, vector, distance }) => {
// drive a canvas, a WebGL uniform, anything
},
});proximitynumber- Eased 0 to 1, the same value as --nf.
distancenumber- Px from the pointer to the nearest edge of the element, 0 inside it.
vector{ x, y }- Unit vector from the element's centre toward the pointer, the same as --nf-x and --nf-y.
localX, localYnumber- Pointer position relative to the element's top-left corner, in px.
width, heightnumber- The element's size from its cached rect.
Magnetic, Tilt, Glow
Three effects on the same field, with the feel already tuned. Each accepts any HTML attributes too.
import { Magnetic, Tilt, Glow } from "near-field";
<Magnetic strength={0.3} feel="gentle">
<button>Get started</button>
</Magnetic>
<Tilt max={6}>...</Tilt>
<Glow color="rgb(59 130 246 / 0.3)">...</Glow>Magnetic
Pulls its content toward the cursor inside the field, then springs back when the pointer leaves.
strengthnumber = 0.3- How far it pulls toward the cursor, as a fraction of the offset, from 0 to 1.
radiusnumber = 140- Field radius in px.
feel"gentle" | "snappy" | "instant" = "gentle"- Spring preset. gentle and snappy are tuned springs; instant follows the pointer with no spring.
asElementType = "span"- Element to render.
Tilt
Tilts toward the cursor, eased in by proximity so the tilt starts before the pointer arrives.
maxnumber = 6- Maximum tilt in degrees.
perspectivenumber = 800- Perspective depth in px.
radiusnumber = 140- Field radius in px.
feel"gentle" | "snappy" | "instant" = "gentle"- Spring preset. gentle and snappy are tuned springs; instant follows the pointer with no spring.
asElementType = "div"- Element to render.
Glow
A soft light that tracks the cursor across the element and brightens as it nears.
colorstring = "rgba(255, 255, 255, 0.25)"- Glow color. Any CSS color; keep the alpha low.
sizenumber = 160- Gradient radius in px.
radiusnumber = 140- Field radius in px.
feel"gentle" | "snappy" | "instant" = "gentle"- Spring preset. gentle and snappy are tuned springs; instant follows the pointer with no spring.
respectReducedMotionboolean = true- Hide under prefers-reduced-motion. Glow only changes opacity, so set false to keep it.
asElementType = "span"- Element to render.
Vanilla
The same primitive and effects with no framework, about 3 KB min+gzip.
import {
nearField,
magnetic,
glow,
} from "near-field/vanilla";
const card = document.querySelector(".card");
const cta = document.querySelector(".cta");
// same options as the hook, no React bundled
const stop = nearField(card, { radius: 140 });
// effects attach to the element you have
magnetic(cta, { strength: 0.3 });
glow(card);
// every call returns a cleanup
stop();Import from near-field/vanilla. Each function takes an element you already have and returns a cleanup that unregisters it and restores its inline styles. The options match the hook and components, minus as and disabled. The entry also re-exports the core (registerElement, computeFrame, createSpring and friends) for building your own adapter.
Touch and motion
- The field only listens to hovering fine pointers, so on touch screens it never activates. Magnetic and Tilt answer a press instead, and Glow shows a centred light while pressed.
- Want a finger to drive it, like the demos on this site? Feed pointer events through the exported
computeFrameand write the properties yourself. - Under
prefers-reduced-motion, Magnetic and Tilt collapse to rest and Glow hides unless you opt in.useNearFieldkeeps writing its properties, so respect the media query in your own CSS. - SSR-safe: nothing touches window at import time.
For AI tools
llms.txt has the whole API in one plain-text file, ready to paste into a coding assistant.
Try it in the labTurn the knobs on the components and the raw primitive.