---
name: grid-glow-background
description: Use when you want "Techy, structured, futuristic" - Combines a grid pattern with glowing highlights.
---

# Grid Glow Background

> **Category:** Background  -  **Personality:** Techy, structured, futuristic
>
> **Best use:** Developer tools, AI, cybersecurity
>
> **Ratings:** Professional 4/5 - Casual 3/5 - Exotic 4/5 - Visual impact 4/5 - Difficulty 2/5 - Performance cost 2/5

## What it is

A flat (non-perspective) technical grid drawn purely with CSS gradients, where a **brighter copy of the grid is revealed only around the cursor** by a radial `mask-image`, plus a soft additive light blob and a few cells that occasionally pulse. The effect reads like a live telemetry surface, which is why developer tools, AI products and security dashboards reach for it. Unlike a "cyber grid" floor (a 3-D perspective plane vanishing to the horizon), this grid stays flat and structural — the drama comes from the moving glow, not the geometry.

## Dependencies / CDN

None — pure CSS for the grid/glow, a tiny bit of vanilla JS to move the highlight toward the pointer. The demo only loads display + mono fonts (optional):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
```

## HTML

Three stacked layers behind your content: the dim base grid, the masked bright grid, and the light blob.

```html
<div class="stage" id="stage">
  <div class="grid"     aria-hidden="true"></div>  <!-- dim base grid       -->
  <div class="glowgrid" aria-hidden="true"></div>  <!-- bright, masked grid  -->
  <div class="light"    aria-hidden="true"></div>  <!-- soft light under cursor -->
  <span class="cell" style="left:40px;top:150px"></span>   <!-- optional pulsing cells -->
  <span class="cell warn" style="left:760px;top:470px;animation-delay:5s"></span>

  <div class="content"><!-- your hero / UI sits here --></div>
</div>
```

## CSS

```css
.stage{position:relative; overflow:hidden; border-radius:26px; min-height:620px;
  isolation:isolate; --mx:62%; --my:30%;          /* glow position (px set by JS) */
  background:radial-gradient(150% 130% at 62% -15%,#0b1422,#04060c 100%); color:#e9f1f4}
.grid,.glowgrid,.light{position:absolute; inset:0; pointer-events:none}

/* 1) dim base grid: fine 40px lines + brighter 200px "major" lines */
.grid{background-image:
    linear-gradient(to right ,rgba(99,214,224,.05) 1px,transparent 1px),
    linear-gradient(to bottom,rgba(99,214,224,.05) 1px,transparent 1px),
    linear-gradient(to right ,rgba(99,214,224,.12) 1px,transparent 1px),
    linear-gradient(to bottom,rgba(99,214,224,.12) 1px,transparent 1px);
  background-size:40px 40px,40px 40px,200px 200px,200px 200px}

/* 2) THE EFFECT — same grid, brighter, revealed only near the pointer by a radial mask */
.glowgrid{background-image:
    linear-gradient(to right ,rgba(72,242,222,.55) 1px,transparent 1px),
    linear-gradient(to bottom,rgba(72,242,222,.55) 1px,transparent 1px);
  background-size:40px 40px;
  -webkit-mask-image:radial-gradient(270px circle at var(--mx) var(--my),#000 0%,rgba(0,0,0,.5) 42%,transparent 72%);
          mask-image:radial-gradient(270px circle at var(--mx) var(--my),#000 0%,rgba(0,0,0,.5) 42%,transparent 72%)}

/* 3) soft additive light blob that follows the same variables */
.light{mix-blend-mode:screen; background:
  radial-gradient(300px circle at var(--mx) var(--my),rgba(72,242,222,.17),transparent 66%)}

/* 4) occasional pulsing cells, snapped to the 40px grid */
.cell{position:absolute; width:40px; height:40px; border-radius:5px; opacity:0;
  background:linear-gradient(180deg,rgba(72,242,222,.18),rgba(72,242,222,.04));
  box-shadow:inset 0 0 0 1px rgba(72,242,222,.5),0 0 16px rgba(72,242,222,.32);
  animation:pulse 6.5s ease-in-out infinite}
.cell.warn{background:linear-gradient(180deg,rgba(251,191,36,.2),rgba(251,191,36,.05));
  box-shadow:inset 0 0 0 1px rgba(251,191,36,.6),0 0 20px rgba(251,191,36,.42)}
@keyframes pulse{0%{opacity:0;transform:scale(.74)}6%{opacity:1;transform:scale(1)}
  20%{opacity:.5}32%{opacity:0;transform:scale(1.03)}100%{opacity:0}}

.content{position:relative; z-index:2}

/* graceful fallback where CSS masks aren't supported */
@supports not ((-webkit-mask-image:radial-gradient(#000,#000)) or (mask-image:radial-gradient(#000,#000))){
  .glowgrid{opacity:.1}}

/* reduced motion: static grid + soft static glow + a few statically-lit cells */
@media (prefers-reduced-motion:reduce){ .cell{opacity:.5; animation:none} }
```

## JavaScript

Updates `--mx` / `--my` toward the pointer, smoothed with a `requestAnimationFrame` lerp so the glow trails instead of snapping. Bail out under reduced motion and leave the static CSS default.

```js
var stage=document.getElementById('stage');
if(stage && !matchMedia('(prefers-reduced-motion: reduce)').matches){
  var r=stage.getBoundingClientRect();
  var tx=r.width*0.62, ty=r.height*0.30, cx=tx, cy=ty, raf=null;
  function paint(){ stage.style.setProperty('--mx',cx.toFixed(1)+'px');
                    stage.style.setProperty('--my',cy.toFixed(1)+'px'); }
  function loop(){ cx+=(tx-cx)*0.16; cy+=(ty-cy)*0.16; paint();
    raf=(Math.abs(tx-cx)>0.4||Math.abs(ty-cy)>0.4)?requestAnimationFrame(loop):null; }
  function kick(){ if(!raf) raf=requestAnimationFrame(loop); }
  paint();
  stage.addEventListener('pointermove',function(e){
    var b=stage.getBoundingClientRect(); tx=e.clientX-b.left; ty=e.clientY-b.top; kick(); });
  stage.addEventListener('pointerleave',function(){
    var b=stage.getBoundingClientRect(); tx=b.width*0.62; ty=b.height*0.30; kick(); });
}
```

## How it works

- **Two identical grids, one masked.** Both `.grid` and `.glowgrid` are the *same* `linear-gradient` line pattern; the base is dim, the glow copy is bright. A `radial-gradient` `mask-image` on the bright copy is opaque (`#000`) at the cursor and fades to `transparent`, so only the grid *inside that circle* is painted — that's the "brighter grid near the pointer".
- **CSS custom properties carry the pointer.** The mask centre and the light blob both read `var(--mx) var(--my)`, so one JS write moves everything together.
- **rAF lerp = trailing glow.** Pointer events set a *target*; the animation frame eases the current position toward it (`+= (target-current)*0.16`), giving smooth motion and naturally throttling DOM writes to one per frame.
- **Pulsing cells** are 40px squares positioned on multiples of the 40px cell size, so they line up exactly with the grid; a staggered keyframe makes them ignite and fade like live activity.

## Customization

- **Density:** change `40px` (and the `200px` major lines) in every `background-size`. Keep cell `left`/`top` on the same multiple so cells stay snapped.
- **Glow radius / falloff:** the `270px` in the mask and `300px` in `.light` set how much grid lights up; the `42%`/`72%` stops shape the edge softness.
- **Colour:** swap `rgba(72,242,222,…)` (cyan) for any accent — e.g. violet `rgba(140,165,255,…)` or amber for an "alert" grid. Tint the base lines the same hue at low alpha.
- **Calmer / busier:** drop the `.glowgrid` for a plain glowing grid, or add more `.cell`s (vary `animation-delay` / `animation-duration`) for more ambient pulses.

## Accessibility & performance

- Cheap: no canvas/WebGL, no per-pixel work. The only runtime cost is one `requestAnimationFrame` loop that writes two custom properties — it stops itself once the glow reaches the target.
- All layers are `pointer-events:none` and `aria-hidden`, so the grid never traps clicks or reaches the accessibility tree.
- `@media (prefers-reduced-motion: reduce)` drops the JS loop (static glow at the default `--mx/--my`) and freezes the pulses, leaving a calm static grid.
- Keep foreground text legible with a `text-shadow` and a vignette/gradient over the grid; the moving glow can otherwise cross headlines.

## Gotchas

- **`mask-image` needs the `-webkit-` prefix** for Safari/iOS and WebKit-based browsers — ship both lines or the reveal silently fails (include the `@supports` fallback shown above).
- **Snap your cells.** A pulsing cell only looks "on the grid" if its `left`/`top` are exact multiples of the cell size *and* the grid's `background-position` is the default `0 0`.
- **Use stage-relative coordinates.** Convert the pointer with `getBoundingClientRect()` (`clientX - rect.left`), not `offsetX` (which is wrong when the pointer is over a child) and not page coordinates (which break once the page scrolls).
- **Don't animate the mask radius or blur** — animate the *position* only. Resizing the gradient each frame is far heavier than moving it.
- A parent with `overflow:hidden` + `border-radius` should set `isolation:isolate` so `mix-blend-mode:screen` on `.light` blends within the stage, not against the page behind it.
