18 CSS Gradient UI16 / 18

CSS + JSMIT licensed

Radial Spotlight / Cursor-Tracking Card

The signature "AI dashboard" card grid: a radial spotlight follows your cursor across each card while a gradient seam glows on the borders nearest the pointer — 12 lines of JS write two custom properties; CSS does everything else.

Published

Live Demo
Try it

The code

<div class="cgu-16-group">
<section class="cgu-16" aria-label="Radial spotlight cursor tracking card demos">
  <div class="cgu-16__inner">
    <header class="cgu-16__head">
      <p class="cgu-16__eyebrow">16 · Cursor spotlight</p>
      <h2 class="cgu-16__title">One light source, four cards</h2>
      <p class="cgu-16__sub">Move your cursor across the grid — the spotlight and border seam track it continuously, bleeding across card edges like a single lamp.</p>
    </header>
    <div class="cgu-16__grid" id="cgu-16-grid">
      <article class="cgu-16__card">
        <p class="cgu-16__label">Model</p>
        <h3 class="cgu-16__name">forge-2.1-large</h3>
        <p class="cgu-16__desc">General reasoning · 400k context</p>
        <p class="cgu-16__stat">18.4k <span>req/day</span></p>
      </article>
      <article class="cgu-16__card">
        <p class="cgu-16__label">Latency p95</p>
        <h3 class="cgu-16__name">312 ms</h3>
        <p class="cgu-16__desc">All regions, streaming first token</p>
        <p class="cgu-16__stat">▼ 9% <span>vs last week</span></p>
      </article>
      <article class="cgu-16__card">
        <p class="cgu-16__label">Spend</p>
        <h3 class="cgu-16__name">$4,218</h3>
        <p class="cgu-16__desc">Month to date · cap $6,000</p>
        <p class="cgu-16__stat">70% <span>of budget</span></p>
      </article>
      <article class="cgu-16__card">
        <p class="cgu-16__label">Fine-tunes</p>
        <h3 class="cgu-16__name">7 active</h3>
        <p class="cgu-16__desc">2 training · 1 queued</p>
        <p class="cgu-16__stat">98.1% <span>eval pass</span></p>
      </article>
    </div>
    <p class="cgu-16__foot">JS writes two custom properties per card; both the face spotlight and the border seam are plain CSS gradients reading them.</p>
  </div>
</section>
</div>
.cgu-16-group {
  display: block;
  width: 100%;
}

.cgu-16,
.cgu-16 *,
.cgu-16 *::before,
.cgu-16 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cgu-16 {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 64px 24px;
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: #eef0ff;
  background: #08090f;
}

.cgu-16__inner {
  width: min(940px,100%);
  display: grid;
  gap: 38px;
}

.cgu-16__head {
  text-align: center;
  display: grid;
  gap: 13px;
  justify-items: center;
}

.cgu-16__eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: #818cf8;
}

.cgu-16__title {
  font-size: clamp(27px,4.2vw,46px);
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.08;
  text-wrap: balance;
}

.cgu-16__sub {
  max-width: 58ch;
  font-size: 15.5px;
  line-height: 1.6;
  color: #a7abc8;
  text-wrap: pretty;
}

.cgu-16__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(210px,1fr));
  gap: 16px;
}

.cgu-16__card {
  --x: 50%;
  --y: 50%;
  position: relative;
  isolation: isolate;
  display: grid;
  gap: 8px;
  align-content: start;
  padding: 26px 24px;
  border-radius: 18px;
  background: #0d0f1a;
  overflow: hidden;
}

.cgu-16__card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(360px circle at var(--x) var(--y),rgba(199,210,254,.07),transparent 65%);
  pointer-events: none;
}

.cgu-16__card::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  background: radial-gradient(300px circle at var(--x) var(--y),rgba(129,140,248,.9),rgba(232,121,249,.35) 45%,rgba(255,255,255,.07) 75%);
  -webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

.cgu-16__label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #6f7495;
}

.cgu-16__name {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -.01em;
}

.cgu-16__desc {
  font-size: 13px;
  line-height: 1.55;
  color: #9aa0c0;
}

.cgu-16__stat {
  margin-top: 10px;
  font-size: 15px;
  font-weight: 750;
  color: #c7d2fe;
}

.cgu-16__stat span {
  font-size: 12px;
  font-weight: 600;
  color: #6f7495;
  margin-left: 4px;
}

.cgu-16__foot {
  text-align: center;
  font-size: 13px;
  color: #7c81a3;
  max-width: 70ch;
  justify-self: center;
}

@media (hover: none),(pointer: coarse) {
  .cgu-16__card::after {
    background: radial-gradient(360px circle at 50% 0%,rgba(199,210,254,.05),transparent 65%);
  }
}
(() => {
  const grid = document.getElementById('cgu-16-grid'); if (!grid) return;
  if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
  const cards = [...grid.querySelectorAll('.cgu-16__card')];
  let raf = 0, ev = null;
  grid.addEventListener('pointermove', e => {
    ev = e;
    if (!raf) raf = requestAnimationFrame(() => {
      raf = 0;
      for (const card of cards) {
        const r = card.getBoundingClientRect();
        card.style.setProperty('--x', (ev.clientX - r.left).toFixed(0) + 'px');
        card.style.setProperty('--y', (ev.clientY - r.top).toFixed(0) + 'px');
      }
    });
  });
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Gradient UI from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Radial Spotlight / Cursor-Tracking Card
Source: https://codefronts.com/design-styles/css-gradient-ui/radial-spotlight-cursor-tracking-card/

The signature "AI dashboard" card grid: a radial spotlight follows your cursor across each card while a gradient seam glows on the borders nearest the pointer — 12 lines of JS write two custom properties; CSS does everything else.
## HTML
```html
<div class="cgu-16-group">
<section class="cgu-16" aria-label="Radial spotlight cursor tracking card demos">
  <div class="cgu-16__inner">
    <header class="cgu-16__head">
      <p class="cgu-16__eyebrow">16 · Cursor spotlight</p>
      <h2 class="cgu-16__title">One light source, four cards</h2>
      <p class="cgu-16__sub">Move your cursor across the grid — the spotlight and border seam track it continuously, bleeding across card edges like a single lamp.</p>
    </header>
    <div class="cgu-16__grid" id="cgu-16-grid">
      <article class="cgu-16__card">
        <p class="cgu-16__label">Model</p>
        <h3 class="cgu-16__name">forge-2.1-large</h3>
        <p class="cgu-16__desc">General reasoning · 400k context</p>
        <p class="cgu-16__stat">18.4k <span>req/day</span></p>
      </article>
      <article class="cgu-16__card">
        <p class="cgu-16__label">Latency p95</p>
        <h3 class="cgu-16__name">312 ms</h3>
        <p class="cgu-16__desc">All regions, streaming first token</p>
        <p class="cgu-16__stat">▼ 9% <span>vs last week</span></p>
      </article>
      <article class="cgu-16__card">
        <p class="cgu-16__label">Spend</p>
        <h3 class="cgu-16__name">$4,218</h3>
        <p class="cgu-16__desc">Month to date · cap $6,000</p>
        <p class="cgu-16__stat">70% <span>of budget</span></p>
      </article>
      <article class="cgu-16__card">
        <p class="cgu-16__label">Fine-tunes</p>
        <h3 class="cgu-16__name">7 active</h3>
        <p class="cgu-16__desc">2 training · 1 queued</p>
        <p class="cgu-16__stat">98.1% <span>eval pass</span></p>
      </article>
    </div>
    <p class="cgu-16__foot">JS writes two custom properties per card; both the face spotlight and the border seam are plain CSS gradients reading them.</p>
  </div>
</section>
</div>
```
## CSS
```css
.cgu-16-group {
  display: block;
  width: 100%;
}

.cgu-16,
.cgu-16 *,
.cgu-16 *::before,
.cgu-16 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cgu-16 {
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 64px 24px;
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: #eef0ff;
  background: #08090f;
}

.cgu-16__inner {
  width: min(940px,100%);
  display: grid;
  gap: 38px;
}

.cgu-16__head {
  text-align: center;
  display: grid;
  gap: 13px;
  justify-items: center;
}

.cgu-16__eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: #818cf8;
}

.cgu-16__title {
  font-size: clamp(27px,4.2vw,46px);
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.08;
  text-wrap: balance;
}

.cgu-16__sub {
  max-width: 58ch;
  font-size: 15.5px;
  line-height: 1.6;
  color: #a7abc8;
  text-wrap: pretty;
}

.cgu-16__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit,minmax(210px,1fr));
  gap: 16px;
}

.cgu-16__card {
  --x: 50%;
  --y: 50%;
  position: relative;
  isolation: isolate;
  display: grid;
  gap: 8px;
  align-content: start;
  padding: 26px 24px;
  border-radius: 18px;
  background: #0d0f1a;
  overflow: hidden;
}

.cgu-16__card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(360px circle at var(--x) var(--y),rgba(199,210,254,.07),transparent 65%);
  pointer-events: none;
}

.cgu-16__card::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  background: radial-gradient(300px circle at var(--x) var(--y),rgba(129,140,248,.9),rgba(232,121,249,.35) 45%,rgba(255,255,255,.07) 75%);
  -webkit-mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

.cgu-16__label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #6f7495;
}

.cgu-16__name {
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -.01em;
}

.cgu-16__desc {
  font-size: 13px;
  line-height: 1.55;
  color: #9aa0c0;
}

.cgu-16__stat {
  margin-top: 10px;
  font-size: 15px;
  font-weight: 750;
  color: #c7d2fe;
}

.cgu-16__stat span {
  font-size: 12px;
  font-weight: 600;
  color: #6f7495;
  margin-left: 4px;
}

.cgu-16__foot {
  text-align: center;
  font-size: 13px;
  color: #7c81a3;
  max-width: 70ch;
  justify-self: center;
}

@media (hover: none),(pointer: coarse) {
  .cgu-16__card::after {
    background: radial-gradient(360px circle at 50% 0%,rgba(199,210,254,.05),transparent 65%);
  }
}
```

## JavaScript
```js
(() => {
  const grid = document.getElementById('cgu-16-grid'); if (!grid) return;
  if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
  const cards = [...grid.querySelectorAll('.cgu-16__card')];
  let raf = 0, ev = null;
  grid.addEventListener('pointermove', e => {
    ev = e;
    if (!raf) raf = requestAnimationFrame(() => {
      raf = 0;
      for (const card of cards) {
        const r = card.getBoundingClientRect();
        card.style.setProperty('--x', (ev.clientX - r.left).toFixed(0) + 'px');
        card.style.setProperty('--y', (ev.clientY - r.top).toFixed(0) + 'px');
      }
    });
  });
})();
```

How this works

One delegated pointermove listener writes the cursor's position into each card's custom properties:

grid.addEventListener('pointermove', e => {
  for (const card of cards){
    const r = card.getBoundingClientRect();
    card.style.setProperty('--x', (e.clientX - r.left) + 'px');
    card.style.setProperty('--y', (e.clientY - r.top) + 'px');
  }
});

Each card paints two layers from those coordinates: a soft interior spotlight — radial-gradient(360px circle at var(--x) var(--y), rgba(255,255,255,.06), transparent 65%) on the card face — and the border seam, the same radial painted on a demo-03 mask ring so only the border pixels show. Because coordinates update on ALL cards (not just the hovered one), the glow bleeds onto neighbours' near edges — that cross-card continuity is what makes the effect feel like one light source, not per-card tricks.

Make it yours

  • Spotlight radius: the 360px circle — bigger reads as ambient, smaller as a torch.
  • Seam color: the conic pair inside the border ring layer.
  • Feels laggy on huge grids? Only update cards within the pointer's bounding neighbourhood.
  • Touch devices get the resting state — add a scroll-driven variant if mobile needs life.

Gotchas — read before shipping

  • Write coordinates in px, not %, or the circle distorts on non-square cards.
  • The listener belongs on the grid (one), not per-card (n) — delegation keeps INP clean.
  • Spotlight opacity above ~.08 looks like a flashlight in the face; restraint sells it.
  • Gate the whole effect behind (hover: hover) and (pointer: fine) — touch gets the clean resting card.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by text-wrap as this demo is written. The core technique itself goes back further — Chrome 85+.

Custom properties + radial-gradient + mask-composite are Baseline. Without JS the cards keep their resting gradient borders — the effect is pure enhancement.

Techniques used in this demo

Search CodeFronts

Loading…