Proximity Glow Button — Cursor Distance Hover Effect
Proximity Glow Button · Buttons · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="pg-stage">
<p class="pg-hint">Move your cursor around the page — the buttons glow brighter the closer you get, even before touching them</p>
<div class="pg-row">
<button class="pg-btn" data-radius="260"><span>Deploy now</span></button>
<button class="pg-btn" data-radius="180"><span>View docs</span></button>
</div>
</div>* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #06070d; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.pg-stage { display: flex; flex-direction: column; align-items: center; gap: 44px; padding: 40px; }
.pg-hint { font-size: 13px; color: #4b5468; text-align: center; max-width: 380px; }
.pg-row { display: flex; gap: 24px; flex-wrap: wrap; justify-content: center; }
.pg-btn {
position: relative;
padding: 15px 34px;
font-size: 15px; font-weight: 700;
border-radius: 12px;
background: #12141f;
border: 1.5px solid #23273a;
color: #cbd2e1;
cursor: pointer; font-family: inherit;
transition: color 0.2s, border-color 0.2s, transform 0.15s;
/* --pg-glow drives both the border glow (via a radial box-shadow proxy)
and the ::before ambient halo intensity. It is written from JS as a
0..1 value based on cursor distance, continuously, not just on :hover. */
--pg-glow: 0;
box-shadow: 0 0 calc(var(--pg-glow) * 40px) calc(var(--pg-glow) * 4px) rgba(99,102,241, calc(var(--pg-glow) * 0.55));
border-color: color-mix(in srgb, #23273a, #818cf8 calc(var(--pg-glow) * 100%));
}
.pg-btn span { position: relative; z-index: 1; pointer-events: none; }
.pg-btn::before {
content: '';
position: absolute; inset: -1.5px;
border-radius: inherit;
background: radial-gradient(circle at 50% 50%, rgba(129,140,248,0.9), transparent 70%);
opacity: var(--pg-glow);
filter: blur(6px);
z-index: 0;
pointer-events: none;
transition: opacity 0.05s linear;
}
.pg-btn:hover { transform: translateY(-1px); color: #fff; }// Unlike a normal :hover glow, this button reacts to how close the cursor
// is ANYWHERE on the page — the glow ramps up smoothly as the cursor
// approaches from a distance, well before it actually touches the button,
// using a continuous document-wide mousemove listener and per-button
// distance falloff rather than a boolean hover state.
var buttons = Array.prototype.slice.call(document.querySelectorAll('.pg-btn'));
function update(clientX, clientY) {
buttons.forEach(function (btn) {
var radius = parseFloat(btn.dataset.radius) || 200;
var rect = btn.getBoundingClientRect();
var cx = rect.left + rect.width / 2;
var cy = rect.top + rect.height / 2;
var dx = clientX - cx;
var dy = clientY - cy;
var dist = Math.sqrt(dx * dx + dy * dy);
var t = 1 - dist / radius; // 1 at center, 0 at radius edge
t = Math.max(0, Math.min(1, t));
t = t * t; // ease-in: glow builds slowly then accelerates near the button
btn.style.setProperty('--pg-glow', t.toFixed(3));
});
}
document.addEventListener('mousemove', function (e) {
update(e.clientX, e.clientY);
});
document.addEventListener('mouseleave', function () {
buttons.forEach(function (btn) { btn.style.setProperty('--pg-glow', '0'); });
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Proximity Glow Button — Continuous Cursor-Distance Glow Before the Cursor Even Touches It

Most button glow effects are binary: nothing happens until the cursor crosses the button's edge, at which point CSS :hover flips a fixed style on. This effect is continuous instead — the glow intensity is a real number between 0 and 1 that increases smoothly as the cursor approaches from anywhere on the page, so the button visibly "senses" an approaching cursor well before contact, similar in spirit to a magnetic button's pull but applied to light intensity instead of position.
A document-wide listener, not a button-scoped one
The trigger mechanism itself is what makes this different from a hover effect: a single mousemove listener on document fires on every cursor movement anywhere on the page, and for every button on the page it recomputes a distance-based intensity — not just for the button the cursor happens to be over. This lets multiple buttons glow in relation to the same cursor position simultaneously, each with its own falloff radius.
The distance-to-intensity formula
For each button, getBoundingClientRect() gives its center point. The straight-line distance from the cursor to that center is compared against a per-button data-radius attribute (in pixels) — the maximum distance at which any glow is visible at all. The raw ratio t = 1 - dist / radius is clamped to [0, 1], then squared (t = t * t). Squaring is the key perceptual tweak: without it, a cursor that's still fairly far away would already produce a noticeably bright glow; squaring keeps the glow nearly invisible until the cursor is meaningfully close, then it ramps up quickly in the final approach — closer to how a real light source's perceived brightness falls off with distance.
Writing intensity as a CSS custom property
Rather than toggling classes, the JS writes the computed t directly to a CSS custom property, --pg-glow, via style.setProperty. Every visual consequence of proximity — the ambient box-shadow, the border color mixed toward indigo via color-mix(), and the blurred ::before halo's opacity — all reference var(--pg-glow) directly in CSS, so a single JS-written number drives three coordinated visual changes without any additional JS branching.
Per-button radius via data-radius
Each button carries its own data-radius attribute, so different buttons can have different "sensing" distances — a primary CTA might use a larger radius (260px) to feel more magnetic and attention-grabbing than a secondary button (180px).
Resetting when the cursor leaves the page
A mouseleave listener on document (fired when the cursor exits the browser viewport entirely) resets every button's --pg-glow back to 0, since without an active cursor position there's nothing to measure distance against.
Step by step
How to Use
- 1Move the cursor around the pageWithout touching either button, move your cursor closer and farther away — notice the glow ramps up smoothly well before you reach the button.
- 2Compare the two buttonsThe first button has a larger data-radius (260) and starts glowing from farther away than the second (180).
- 3Adjust the sensing radiusIn the HTML panel, change each button's data-radius attribute to control how far away the glow starts appearing.
- 4Change the glow colorUpdate the rgba(99,102,241, ...) values in the box-shadow rule and the color-mix() target color in the CSS panel.
- 5Adjust the falloff curveIn the JS panel, change the t = t * t line — remove it for a linear falloff, or raise the power (t*t*t) for glow that appears even more suddenly at close range.
- 6Export 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
Got questions?
Frequently Asked Questions
A :hover glow is binary — off until the cursor crosses the button's exact boundary, then fully on. This effect computes a continuous distance value on every mousemove anywhere on the page, so the glow visibly ramps up as the cursor approaches from far away, well before it touches the button.
A raw linear ratio makes the glow noticeably visible even when the cursor is still fairly far from the button. Squaring keeps the glow nearly imperceptible at greater distances and lets it build up quickly only in the final approach, which reads as a more natural, light-source-like falloff.
It is the maximum distance in pixels, measured from the button's center to the cursor, at which any glow starts to appear at all. A button with a 260px radius starts glowing from farther away than one with a 180px radius.
Writing one number to --pg-glow lets CSS itself compute three coordinated effects (box-shadow spread and opacity, color-mix on the border, and a blurred halo's opacity) directly from that single value using CSS functions like calc() and color-mix() — simpler and faster than computing and writing three separate style properties from JavaScript every mousemove.
Each mousemove event does a getBoundingClientRect() and a distance calculation per button, which is cheap for a handful of buttons. For dozens of proximity-reactive elements on one page, consider throttling the mousemove handler or caching bounding rects and only recomputing them on scroll/resize.
Yes. Click "JSX" for a React component. Attach a single document mousemove listener in a useEffect with an empty dependency array, keep refs to each button, and write --pg-glow directly via ref.current.style.setProperty inside the handler to avoid state-driven re-renders on every mouse movement.