20 CSS Neon Text Effects14 / 20
CSS Variables Dynamic Neon Colour
One registered @property --hue drives the entire glow spectrum — text colour and every shadow layer — so a single slider retints the whole sign live. The clean-token setup front-end engineers search for.
Published
The code
<div class="cnt-14">
<div class="cnt-14__stack">
<h2 class="cnt-14__h" id="cnt-14-sign">SPECTRUM</h2>
<label class="cnt-14__ctrl">hue
<input id="cnt-14-range" type="range" min="0" max="360" value="200" aria-label="Neon hue">
</label>
</div>
</div><div class="cnt-14">
<div class="cnt-14__stack">
<h2 class="cnt-14__h" id="cnt-14-sign">SPECTRUM</h2>
<label class="cnt-14__ctrl">hue
<input id="cnt-14-range" type="range" min="0" max="360" value="200" aria-label="Neon hue">
</label>
</div>
</div>.cnt-14,
.cnt-14 *,
.cnt-14 *::before,
.cnt-14 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cnt-14 ::selection {
background: #fff;
color: #000;
}
@property --hue {
syntax: '<angle>';
inherits: true;
initial-value: 200deg;
}
.cnt-14 {
--hue: 200deg;
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 20px;
background: radial-gradient(120% 120% at 50% 30%,#0a0a0e,#050506);
}
.cnt-14__stack {
display: flex;
flex-direction: column;
align-items: center;
gap: 34px;
}
.cnt-14__h {
--c: oklch(0.82 0.19 var(--hue));
font-size: clamp(46px,12vw,116px);
font-weight: 900;
letter-spacing: .05em;
color: var(--c);
text-shadow: 0 0 5px #fff,0 0 14px var(--c),0 0 34px var(--c),0 0 72px color-mix(in oklch,var(--c) 55%,transparent);
animation: cnt-14-cycle 12s linear infinite;
}
@keyframes cnt-14-cycle {
to {
--hue: 560deg;
}
}
.cnt-14__ctrl {
display: flex;
align-items: center;
gap: 14px;
font: 12px ui-monospace,Menlo,monospace;
letter-spacing: .24em;
text-transform: uppercase;
color: #8a8a95;
}
.cnt-14__ctrl input {
width: 220px;
accent-color: oklch(0.82 0.19 var(--hue));
}
@media (prefers-reduced-motion: reduce) {
.cnt-14__h {
animation: none;
}
}.cnt-14,
.cnt-14 *,
.cnt-14 *::before,
.cnt-14 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cnt-14 ::selection {
background: #fff;
color: #000;
}
@property --hue {
syntax: '<angle>';
inherits: true;
initial-value: 200deg;
}
.cnt-14 {
--hue: 200deg;
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 20px;
background: radial-gradient(120% 120% at 50% 30%,#0a0a0e,#050506);
}
.cnt-14__stack {
display: flex;
flex-direction: column;
align-items: center;
gap: 34px;
}
.cnt-14__h {
--c: oklch(0.82 0.19 var(--hue));
font-size: clamp(46px,12vw,116px);
font-weight: 900;
letter-spacing: .05em;
color: var(--c);
text-shadow: 0 0 5px #fff,0 0 14px var(--c),0 0 34px var(--c),0 0 72px color-mix(in oklch,var(--c) 55%,transparent);
animation: cnt-14-cycle 12s linear infinite;
}
@keyframes cnt-14-cycle {
to {
--hue: 560deg;
}
}
.cnt-14__ctrl {
display: flex;
align-items: center;
gap: 14px;
font: 12px ui-monospace,Menlo,monospace;
letter-spacing: .24em;
text-transform: uppercase;
color: #8a8a95;
}
.cnt-14__ctrl input {
width: 220px;
accent-color: oklch(0.82 0.19 var(--hue));
}
@media (prefers-reduced-motion: reduce) {
.cnt-14__h {
animation: none;
}
}(function(){
var r = document.getElementById('cnt-14-range');
var sign = document.getElementById('cnt-14-sign');
if(!r || !sign) return;
r.addEventListener('input', function(){
// scrubbing overrides the idle cycle with an explicit hue
sign.style.animation = 'none';
sign.style.setProperty('--hue', r.value + 'deg');
});
})();(function(){
var r = document.getElementById('cnt-14-range');
var sign = document.getElementById('cnt-14-sign');
if(!r || !sign) return;
r.addEventListener('input', function(){
// scrubbing overrides the idle cycle with an explicit hue
sign.style.animation = 'none';
sign.style.setProperty('--hue', r.value + 'deg');
});
})();Here's a working CSS Neon Text Effect 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: CSS Variables Dynamic Neon Colour
Source: https://codefronts.com/motion/css-neon-text-effect/css-variables-dynamic-neon-colour/
One registered @property --hue drives the entire glow spectrum — text colour and every shadow layer — so a single slider retints the whole sign live. The clean-token setup front-end engineers search for.
## HTML
```html
<div class="cnt-14">
<div class="cnt-14__stack">
<h2 class="cnt-14__h" id="cnt-14-sign">SPECTRUM</h2>
<label class="cnt-14__ctrl">hue
<input id="cnt-14-range" type="range" min="0" max="360" value="200" aria-label="Neon hue">
</label>
</div>
</div>
```
## CSS
```css
.cnt-14,
.cnt-14 *,
.cnt-14 *::before,
.cnt-14 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cnt-14 ::selection {
background: #fff;
color: #000;
}
@property --hue {
syntax: '<angle>';
inherits: true;
initial-value: 200deg;
}
.cnt-14 {
--hue: 200deg;
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 20px;
background: radial-gradient(120% 120% at 50% 30%,#0a0a0e,#050506);
}
.cnt-14__stack {
display: flex;
flex-direction: column;
align-items: center;
gap: 34px;
}
.cnt-14__h {
--c: oklch(0.82 0.19 var(--hue));
font-size: clamp(46px,12vw,116px);
font-weight: 900;
letter-spacing: .05em;
color: var(--c);
text-shadow: 0 0 5px #fff,0 0 14px var(--c),0 0 34px var(--c),0 0 72px color-mix(in oklch,var(--c) 55%,transparent);
animation: cnt-14-cycle 12s linear infinite;
}
@keyframes cnt-14-cycle {
to {
--hue: 560deg;
}
}
.cnt-14__ctrl {
display: flex;
align-items: center;
gap: 14px;
font: 12px ui-monospace,Menlo,monospace;
letter-spacing: .24em;
text-transform: uppercase;
color: #8a8a95;
}
.cnt-14__ctrl input {
width: 220px;
accent-color: oklch(0.82 0.19 var(--hue));
}
@media (prefers-reduced-motion: reduce) {
.cnt-14__h {
animation: none;
}
}
```
## JavaScript
```js
(function(){
var r = document.getElementById('cnt-14-range');
var sign = document.getElementById('cnt-14-sign');
if(!r || !sign) return;
r.addEventListener('input', function(){
// scrubbing overrides the idle cycle with an explicit hue
sign.style.animation = 'none';
sign.style.setProperty('--hue', r.value + 'deg');
});
})();
```Here's a working CSS Neon Text Effect 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: CSS Variables Dynamic Neon Colour
Source: https://codefronts.com/motion/css-neon-text-effect/css-variables-dynamic-neon-colour/
One registered @property --hue drives the entire glow spectrum — text colour and every shadow layer — so a single slider retints the whole sign live. The clean-token setup front-end engineers search for.
## HTML
```html
<div class="cnt-14">
<div class="cnt-14__stack">
<h2 class="cnt-14__h" id="cnt-14-sign">SPECTRUM</h2>
<label class="cnt-14__ctrl">hue
<input id="cnt-14-range" type="range" min="0" max="360" value="200" aria-label="Neon hue">
</label>
</div>
</div>
```
## CSS
```css
.cnt-14,
.cnt-14 *,
.cnt-14 *::before,
.cnt-14 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cnt-14 ::selection {
background: #fff;
color: #000;
}
@property --hue {
syntax: '<angle>';
inherits: true;
initial-value: 200deg;
}
.cnt-14 {
--hue: 200deg;
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 60px 20px;
background: radial-gradient(120% 120% at 50% 30%,#0a0a0e,#050506);
}
.cnt-14__stack {
display: flex;
flex-direction: column;
align-items: center;
gap: 34px;
}
.cnt-14__h {
--c: oklch(0.82 0.19 var(--hue));
font-size: clamp(46px,12vw,116px);
font-weight: 900;
letter-spacing: .05em;
color: var(--c);
text-shadow: 0 0 5px #fff,0 0 14px var(--c),0 0 34px var(--c),0 0 72px color-mix(in oklch,var(--c) 55%,transparent);
animation: cnt-14-cycle 12s linear infinite;
}
@keyframes cnt-14-cycle {
to {
--hue: 560deg;
}
}
.cnt-14__ctrl {
display: flex;
align-items: center;
gap: 14px;
font: 12px ui-monospace,Menlo,monospace;
letter-spacing: .24em;
text-transform: uppercase;
color: #8a8a95;
}
.cnt-14__ctrl input {
width: 220px;
accent-color: oklch(0.82 0.19 var(--hue));
}
@media (prefers-reduced-motion: reduce) {
.cnt-14__h {
animation: none;
}
}
```
## JavaScript
```js
(function(){
var r = document.getElementById('cnt-14-range');
var sign = document.getElementById('cnt-14-sign');
if(!r || !sign) return;
r.addEventListener('input', function(){
// scrubbing overrides the idle cycle with an explicit hue
sign.style.animation = 'none';
sign.style.setProperty('--hue', r.value + 'deg');
});
})();
```How this works
A registered @property --hue (typed <angle>) is the single source of truth. The label's color and its whole text-shadow stack are built from oklch(... var(--hue)) and color-mix(), so changing that one number rotates every layer's hue in perfect sync — no per-colour rules anywhere.
Because --hue is a real typed property it can also be animated — the idle state slowly cycles the hue via a @keyframes. The one line of JS just points the range input at --hue so users can scrub it; remove the input and the CSS animation still runs.
Make it yours
--hueis the only knob — wire it to a slider, a scroll position, or leave the idle cycle running.- Lock lightness/chroma in the
oklch()calls so every hue stays equally punchy. - Swap the cycle for a fixed brand hue by deleting the
@keyframesand setting--hueonce. - Add a second registered property (
--glow-size) for an independent intensity slider.
Gotchas — read before shipping
- Animating/transitioning a custom property requires it be registered with
@propertyand a typed syntax — a plain--huewon't interpolate. @propertyis the newest feature here — guard non-critical use and provide a sensibleinitial-value.- Keep the slider labelled and keyboard-operable; it's a real form control, not decoration.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Floor set by oklch(), color-mix(), @property as this demo is written. The core technique itself goes back further — Chrome 85+.
@property registration: Chrome 85+, Safari 16.4+, Firefox 128+. Without it the initial-value hue shows statically.