20 CSS Gradient Text Designs 17 / 20
CSS Cursor-Tracked Radial Gradient Text
A radial gradient spotlight follows the cursor position inside the demo, illuminating text from the exact point the mouse hovers, driven by CSS custom properties updated in JavaScript.
The code
<div class="gt-17" id="gt-17-root">
<div class="gt-17__cursor" id="gt-17-cursor"></div>
<span class="gt-17__label">Cursor-tracked gradient</span>
<h1 class="gt-17__main" id="gt-17-main">FOLLOW</h1>
<p class="gt-17__sub" id="gt-17-sub">Move your cursor</p>
<div class="gt-17__grid">
<span class="gt-17__cell">TRACK</span>
<span class="gt-17__cell">LIGHT</span>
<span class="gt-17__cell">FOCUS</span>
</div>
</div>
<script>
(function() {
const root = document.getElementById('gt-17-root');
const cursor = document.getElementById('gt-17-cursor');
if (!root) return;
root.addEventListener('mousemove', (e) => {
const rect = root.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width * 100).toFixed(1) + '%';
const y = ((e.clientY - rect.top) / rect.height * 100).toFixed(1) + '%';
root.style.setProperty('--cx', x);
root.style.setProperty('--cy', y);
cursor.style.left = e.clientX + 'px';
cursor.style.top = e.clientY + 'px';
});
})();
</script> <div class="gt-17" id="gt-17-root">
<div class="gt-17__cursor" id="gt-17-cursor"></div>
<span class="gt-17__label">Cursor-tracked gradient</span>
<h1 class="gt-17__main" id="gt-17-main">FOLLOW</h1>
<p class="gt-17__sub" id="gt-17-sub">Move your cursor</p>
<div class="gt-17__grid">
<span class="gt-17__cell">TRACK</span>
<span class="gt-17__cell">LIGHT</span>
<span class="gt-17__cell">FOCUS</span>
</div>
</div>
<script>
(function() {
const root = document.getElementById('gt-17-root');
const cursor = document.getElementById('gt-17-cursor');
if (!root) return;
root.addEventListener('mousemove', (e) => {
const rect = root.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width * 100).toFixed(1) + '%';
const y = ((e.clientY - rect.top) / rect.height * 100).toFixed(1) + '%';
root.style.setProperty('--cx', x);
root.style.setProperty('--cy', y);
cursor.style.left = e.clientX + 'px';
cursor.style.top = e.clientY + 'px';
});
})();
</script>.gt-17, .gt-17 *, .gt-17 *::before, .gt-17 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.gt-17 {
--bg: #08080e;
--cx: 50%;
--cy: 50%;
font-family: 'Space Grotesk', sans-serif;
background: var(--bg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2.5rem;
padding: 3rem 2rem;
cursor: none;
}
.gt-17__cursor {
position: fixed;
top: 0; left: 0;
width: 12px; height: 12px;
border-radius: 50%;
background: white;
mix-blend-mode: exclusion;
pointer-events: none;
transform: translate(-50%, -50%);
transition: transform .1s;
z-index: 9999;
}
.gt-17__label {
font-size: .7rem;
letter-spacing: .2em;
text-transform: uppercase;
color: #333;
}
.gt-17__main {
font-size: clamp(3rem, 12vw, 8rem);
font-weight: 800;
line-height: 1;
letter-spacing: -.01em;
background: radial-gradient(
circle at var(--cx) var(--cy),
#a855f7 0%,
#ec4899 25%,
#f43f5e 50%,
#fb923c 75%,
#fbbf24 100%
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
transition: --cx .05s, --cy .05s;
}
.gt-17__sub {
font-size: clamp(.9rem, 2.5vw, 1.3rem);
font-weight: 700;
background: radial-gradient(
circle at var(--cx) var(--cy),
#00f5a0 0%,
#00d9f5 50%,
#4d96ff 100%
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: .1em;
}
.gt-17__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.gt-17__cell {
font-size: clamp(1rem, 3vw, 1.6rem);
font-weight: 800;
padding: .6em 1em;
text-align: center;
border: 1px solid #ffffff0d;
border-radius: 8px;
background: radial-gradient(
circle at var(--cx) var(--cy),
#a855f720 0%,
transparent 70%
);
color: #333;
background-clip: padding-box;
-webkit-text-fill-color: transparent;
background-image: radial-gradient(
circle at var(--cx) var(--cy),
#a855f7 0%,
#ec4899 40%,
#4d96ff 80%,
#00f5a0 100%
);
-webkit-background-clip: text;
}
@media (prefers-reduced-motion: reduce) {
.gt-17__cursor { display: none; }
} .gt-17, .gt-17 *, .gt-17 *::before, .gt-17 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.gt-17 {
--bg: #08080e;
--cx: 50%;
--cy: 50%;
font-family: 'Space Grotesk', sans-serif;
background: var(--bg);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2.5rem;
padding: 3rem 2rem;
cursor: none;
}
.gt-17__cursor {
position: fixed;
top: 0; left: 0;
width: 12px; height: 12px;
border-radius: 50%;
background: white;
mix-blend-mode: exclusion;
pointer-events: none;
transform: translate(-50%, -50%);
transition: transform .1s;
z-index: 9999;
}
.gt-17__label {
font-size: .7rem;
letter-spacing: .2em;
text-transform: uppercase;
color: #333;
}
.gt-17__main {
font-size: clamp(3rem, 12vw, 8rem);
font-weight: 800;
line-height: 1;
letter-spacing: -.01em;
background: radial-gradient(
circle at var(--cx) var(--cy),
#a855f7 0%,
#ec4899 25%,
#f43f5e 50%,
#fb923c 75%,
#fbbf24 100%
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
transition: --cx .05s, --cy .05s;
}
.gt-17__sub {
font-size: clamp(.9rem, 2.5vw, 1.3rem);
font-weight: 700;
background: radial-gradient(
circle at var(--cx) var(--cy),
#00f5a0 0%,
#00d9f5 50%,
#4d96ff 100%
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: .1em;
}
.gt-17__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.gt-17__cell {
font-size: clamp(1rem, 3vw, 1.6rem);
font-weight: 800;
padding: .6em 1em;
text-align: center;
border: 1px solid #ffffff0d;
border-radius: 8px;
background: radial-gradient(
circle at var(--cx) var(--cy),
#a855f720 0%,
transparent 70%
);
color: #333;
background-clip: padding-box;
-webkit-text-fill-color: transparent;
background-image: radial-gradient(
circle at var(--cx) var(--cy),
#a855f7 0%,
#ec4899 40%,
#4d96ff 80%,
#00f5a0 100%
);
-webkit-background-clip: text;
}
@media (prefers-reduced-motion: reduce) {
.gt-17__cursor { display: none; }
}(function() {
const root = document.getElementById('gt-17-root');
const cursor = document.getElementById('gt-17-cursor');
if (!root) return;
root.addEventListener('mousemove', (e) => {
const rect = root.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width * 100).toFixed(1) + '%';
const y = ((e.clientY - rect.top) / rect.height * 100).toFixed(1) + '%';
root.style.setProperty('--cx', x);
root.style.setProperty('--cy', y);
cursor.style.left = e.clientX + 'px';
cursor.style.top = e.clientY + 'px';
});
})(); (function() {
const root = document.getElementById('gt-17-root');
const cursor = document.getElementById('gt-17-cursor');
if (!root) return;
root.addEventListener('mousemove', (e) => {
const rect = root.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width * 100).toFixed(1) + '%';
const y = ((e.clientY - rect.top) / rect.height * 100).toFixed(1) + '%';
root.style.setProperty('--cx', x);
root.style.setProperty('--cy', y);
cursor.style.left = e.clientX + 'px';
cursor.style.top = e.clientY + 'px';
});
})();How this works
The demo sets two CSS custom properties — --cx and --cy — on the root element via a mousemove listener that converts pointer coordinates to percentage values relative to the element's bounding rect. Each text element's gradient is defined as radial-gradient(circle at var(--cx) var(--cy), ...), so the gradient origin updates instantly as the cursor moves without any CSS transition overhead.
Custom property writes that feed into gradients are paint operations, not compositor operations, so this technique works best on reasonably-sized elements. A custom CSS cursor (a small white dot with mix-blend-mode: exclusion) replaces the native cursor to reinforce the spotlight metaphor.
Customize
- Add a
transitionon--cxand--cywith Houdini's@propertysyntax to create a lagging gradient that trails behind the cursor for a more cinematic feel. - Replace the radial gradient with a
conic-gradient(from calc(var(--cx) * 3.6deg), ...)to make the gradient spin based on horizontal cursor position. - Limit the effect to elements under the cursor with a
:hoverstate on each cell so only the hovered element lights up.
Watch out for
- Custom property writes on every
mousemoveevent fire at up to 1000Hz on some devices — throttle withrequestAnimationFrameif GPU repaint cost is high. - The gradient re-evaluation happens on the CPU paint thread, not the compositor — avoid using cursor-tracked gradients on large full-bleed backgrounds. Constrain the technique to headline-sized elements.
- The
mix-blend-mode: exclusioncursor requires its own compositing layer; on integrated graphics this may cause frame drops at sustained cursor velocity.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 79+ | 15.4+ | 75+ | 79+ |
Custom property interpolation without @property transitions requires modern browsers; fallback gracefully by setting a static background-position if mousemove is unavailable (touch devices).