20 CSS Gradient Text Designs 15 / 20
CSS Scroll Gradient Text Reveal on Scroll
Each section's headline fades in from below with a gradient fill sliding from washed-out to vivid, triggered by IntersectionObserver as the element enters the viewport.
The code
<div class="gt-15">
<div class="gt-15__top">
<p class="gt-15__hint">Scroll to reveal gradient text</p>
<span class="gt-15__arrow">↓</span>
</div>
<div class="gt-15__section">
<div class="gt-15__reveal">DISCOVER</div>
</div>
<div class="gt-15__section">
<div class="gt-15__reveal">YOUR</div>
</div>
<div class="gt-15__section">
<div class="gt-15__reveal">POTENTIAL</div>
</div>
<div class="gt-15__section">
<div class="gt-15__reveal">THROUGH COLOUR</div>
</div>
</div>
<script>
(function() {
const items = document.querySelectorAll('.gt-15 .gt-15__reveal');
if (!items.length) return;
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('is-visible');
}
});
}, { threshold: 0.3 });
items.forEach(el => io.observe(el));
})();
</script> <div class="gt-15">
<div class="gt-15__top">
<p class="gt-15__hint">Scroll to reveal gradient text</p>
<span class="gt-15__arrow">↓</span>
</div>
<div class="gt-15__section">
<div class="gt-15__reveal">DISCOVER</div>
</div>
<div class="gt-15__section">
<div class="gt-15__reveal">YOUR</div>
</div>
<div class="gt-15__section">
<div class="gt-15__reveal">POTENTIAL</div>
</div>
<div class="gt-15__section">
<div class="gt-15__reveal">THROUGH COLOUR</div>
</div>
</div>
<script>
(function() {
const items = document.querySelectorAll('.gt-15 .gt-15__reveal');
if (!items.length) return;
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('is-visible');
}
});
}, { threshold: 0.3 });
items.forEach(el => io.observe(el));
})();
</script>.gt-15, .gt-15 *, .gt-15 *::before, .gt-15 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.gt-15 {
--bg: #0c0c0c;
font-family: 'Syne', sans-serif;
background: var(--bg);
min-height: 300vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 2rem;
}
.gt-15__top {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
}
.gt-15__hint {
font-size: .8rem;
letter-spacing: .2em;
text-transform: uppercase;
color: #555;
animation: gt-15-bounce 2s ease-in-out infinite;
}
.gt-15__arrow {
font-size: 1.5rem;
color: #444;
animation: gt-15-bounce 2s ease-in-out infinite .3s;
}
.gt-15__section {
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 900px;
}
.gt-15__reveal {
font-size: clamp(3rem, 10vw, 7rem);
font-weight: 800;
line-height: 1;
letter-spacing: -.01em;
color: #2a2a2a;
background: linear-gradient(90deg, #ff6b6b, #ffd93d, #6bcb77, #4d96ff);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
opacity: 0;
transform: translateY(40px);
transition: opacity .8s ease, transform .8s ease;
background-position: 100% center;
}
.gt-15__reveal.is-visible {
opacity: 1;
transform: translateY(0);
background-position: 0% center;
transition: opacity .8s ease, transform .8s ease, background-position 1.2s ease;
}
.gt-15__reveal:nth-child(1) { transition-delay: 0s; }
.gt-15__reveal:nth-child(2) { transition-delay: .1s; }
@keyframes gt-15-bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(8px); }
}
@media (prefers-reduced-motion: reduce) {
.gt-15__reveal {
opacity: 1;
transform: none;
transition: none;
background-position: 0% center;
}
.gt-15__hint, .gt-15__arrow { animation: none; }
} .gt-15, .gt-15 *, .gt-15 *::before, .gt-15 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.gt-15 {
--bg: #0c0c0c;
font-family: 'Syne', sans-serif;
background: var(--bg);
min-height: 300vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 2rem;
}
.gt-15__top {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
}
.gt-15__hint {
font-size: .8rem;
letter-spacing: .2em;
text-transform: uppercase;
color: #555;
animation: gt-15-bounce 2s ease-in-out infinite;
}
.gt-15__arrow {
font-size: 1.5rem;
color: #444;
animation: gt-15-bounce 2s ease-in-out infinite .3s;
}
.gt-15__section {
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 900px;
}
.gt-15__reveal {
font-size: clamp(3rem, 10vw, 7rem);
font-weight: 800;
line-height: 1;
letter-spacing: -.01em;
color: #2a2a2a;
background: linear-gradient(90deg, #ff6b6b, #ffd93d, #6bcb77, #4d96ff);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
opacity: 0;
transform: translateY(40px);
transition: opacity .8s ease, transform .8s ease;
background-position: 100% center;
}
.gt-15__reveal.is-visible {
opacity: 1;
transform: translateY(0);
background-position: 0% center;
transition: opacity .8s ease, transform .8s ease, background-position 1.2s ease;
}
.gt-15__reveal:nth-child(1) { transition-delay: 0s; }
.gt-15__reveal:nth-child(2) { transition-delay: .1s; }
@keyframes gt-15-bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(8px); }
}
@media (prefers-reduced-motion: reduce) {
.gt-15__reveal {
opacity: 1;
transform: none;
transition: none;
background-position: 0% center;
}
.gt-15__hint, .gt-15__arrow { animation: none; }
}(function() {
const items = document.querySelectorAll('.gt-15 .gt-15__reveal');
if (!items.length) return;
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('is-visible');
}
});
}, { threshold: 0.3 });
items.forEach(el => io.observe(el));
})(); (function() {
const items = document.querySelectorAll('.gt-15 .gt-15__reveal');
if (!items.length) return;
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('is-visible');
}
});
}, { threshold: 0.3 });
items.forEach(el => io.observe(el));
})();How this works
Headline elements start with opacity: 0, transform: translateY(40px), and background-position: 100% center — the gradient is off-screen to the right, appearing washed out. An IntersectionObserver with threshold: 0.3 watches each element and adds the .is-visible class when 30% of the element enters the viewport.
The .is-visible rule transitions opacity and transform in 0.8s, and simultaneously transitions background-position from 100% to 0% in 1.2s — a slightly longer duration so the colour washes in after the element has settled into place, creating a two-stage reveal effect.
Customize
- Change the threshold to
0.1for an earlier trigger — the gradient starts washing in as soon as 10% of the element is visible. - Add
io.unobserve(e.target)after addingis-visibleto fire the animation only once per element rather than every time it enters the viewport. - Adjust
translateY(40px)totranslateX(-40px)for a horizontal slide-in that differs from the standard vertical reveal.
Watch out for
background-positiontransition requires the element to have an explicitbackground-sizeset; the defaultautosize makes the position animation non-functional in some browsers.- IntersectionObserver does not fire for elements already in the viewport at page load on some browsers — call
io.observeafter arequestAnimationFramedelay if initial elements fail to reveal. prefers-reduced-motionshould immediately setopacity: 1andtransform: nonewithout transition, so content is accessible to users with vestibular disorders even when JavaScript is active.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 58+ | 12.1+ | 55+ | 58+ |
IntersectionObserver is well-supported in all modern browsers; a simple scroll-event fallback can serve IE11.