16 CSS Gradient Animations 09 / 16

CSS Animated Infinite Gradient Text

Multiple gradient text variants — a hero headline, sentence-level accents, animated stat counters, and a badge tag — all using background-clip: text with a sliding background-position keyframe to create a continuously flowing colour palette across the lettering.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="ga-09">

  <div class="ga-09__tag-display">
    <span class="ga-09__tag-icon">✦</span>
    <span class="ga-09__tag-text ga-09__text">CSS background-clip gradient animation</span>
  </div>

  <div class="ga-09__hero">
    <span class="ga-09__pre">Build the future</span>
    <span class="ga-09__gradient-a ga-09__text">one pixel at a time.</span>
  </div>

  <p class="ga-09__sentence">
    Gradient text is great for
    <span class="ga-09__accent-b ga-09__text">hero headlines</span>,
    key statistics, and
    <span class="ga-09__accent-b ga-09__text" style="background-image:linear-gradient(90deg,#f97316,#eab308,#10b981,#f97316)">call-to-action</span>
    words that need to pop without colour conflicts.
  </p>

  <div class="ga-09__stats">
    <div class="ga-09__stat">
      <span class="ga-09__stat-num ga-09__stat-c1 ga-09__text">4.2M</span>
      <span class="ga-09__stat-label">Monthly users</span>
    </div>
    <div class="ga-09__stat">
      <span class="ga-09__stat-num ga-09__stat-c2 ga-09__text">99.98%</span>
      <span class="ga-09__stat-label">Uptime SLA</span>
    </div>
    <div class="ga-09__stat">
      <span class="ga-09__stat-num ga-09__stat-c3 ga-09__text">18ms</span>
      <span class="ga-09__stat-label">Avg. latency</span>
    </div>
  </div>

  <div class="ga-09__controls">
    <button class="ga-09__ctrl-btn" data-dur="12s">Slow</button>
    <button class="ga-09__ctrl-btn active" data-dur="5s">Normal</button>
    <button class="ga-09__ctrl-btn" data-dur="2.5s">Fast</button>
    <button class="ga-09__ctrl-btn" data-dur="1s">Blazing</button>
  </div>

</div>
.ga-09, .ga-09 *, .ga-09 *::before, .ga-09 *::after {
  margin: 0; padding: 0; box-sizing: border-box;
}
.ga-09 ::selection { background: rgba(168,85,247,.4); color: #fff; }

.ga-09 {
  --bg: #060912;
  --dur: 5s;
  width: 100%;
  min-height: 100vh;
  background: var(--bg);
  font-family: system-ui, -apple-system, sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 48px;
  padding: 48px 24px;
}

/* ── Core gradient text technique ── */
.ga-09__text {
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  background-size: 300% 100%;
  animation: ga-09-slide var(--dur) linear infinite;
}

@keyframes ga-09-slide {
  0%   { background-position: 0% 50%; }
  100% { background-position: -200% 50%; }
}

/* ── Variant A: hero headline ── */
.ga-09__hero {
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ga-09__pre {
  font-size: clamp(2.4rem, 6vw, 4rem);
  font-weight: 900;
  line-height: 1.08;
  letter-spacing: -.035em;
  color: rgba(255,255,255,.9);
}
.ga-09__gradient-a {
  font-size: clamp(2.4rem, 6vw, 4rem);
  font-weight: 900;
  line-height: 1.08;
  letter-spacing: -.035em;
  background-image: linear-gradient(90deg,
    #a855f7, #ec4899, #f97316, #eab308, #10b981, #06b6d4, #6366f1, #a855f7
  );
}
.ga-09__gradient-a.ga-09__text { animation-duration: calc(var(--dur) * 1.4); }

/* ── Variant B: word-level accents in a sentence ── */
.ga-09__sentence {
  font-size: clamp(1.15rem, 2.5vw, 1.5rem);
  font-weight: 600;
  color: rgba(255,255,255,.55);
  line-height: 1.6;
  text-align: center;
  max-width: 580px;
}
.ga-09__accent-b {
  font-weight: 800;
  background-image: linear-gradient(90deg,
    #06b6d4, #818cf8, #ec4899, #f97316, #06b6d4
  );
  display: inline;
}
.ga-09__accent-b.ga-09__text { animation-duration: calc(var(--dur) * .8); }

/* ── Variant C: display counters / stats ── */
.ga-09__stats {
  display: flex;
  gap: 40px;
  flex-wrap: wrap;
  justify-content: center;
}
.ga-09__stat {
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ga-09__stat-num {
  font-size: clamp(2rem, 4vw, 2.8rem);
  font-weight: 900;
  letter-spacing: -.03em;
  line-height: 1;
}
.ga-09__stat-c1 { background-image: linear-gradient(90deg, #06b6d4, #6366f1, #a855f7, #06b6d4); }
.ga-09__stat-c2 { background-image: linear-gradient(90deg, #10b981, #06b6d4, #818cf8, #10b981); animation-delay: -.8s; }
.ga-09__stat-c3 { background-image: linear-gradient(90deg, #f97316, #ec4899, #a855f7, #f97316); animation-delay: -1.6s; }
.ga-09__stat-label {
  font-size: .7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: rgba(255,255,255,.3);
}

/* ── Variant D: large display tag ── */
.ga-09__tag-display {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 20px;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,.08);
  background: rgba(255,255,255,.03);
}
.ga-09__tag-icon { font-size: .9rem; }
.ga-09__tag-text {
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .05em;
  background-image: linear-gradient(90deg, #c084fc, #818cf8, #67e8f9, #34d399, #c084fc);
}

/* Speed palette buttons */
.ga-09__controls {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}
.ga-09__ctrl-btn {
  padding: 5px 14px;
  border-radius: 7px;
  font-size: .73rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  border: 1px solid rgba(255,255,255,.1);
  background: rgba(255,255,255,.05);
  color: rgba(255,255,255,.4);
  cursor: pointer;
  transition: all .2s;
}
.ga-09__ctrl-btn.active,
.ga-09__ctrl-btn:hover {
  background: rgba(168,85,247,.15);
  border-color: rgba(168,85,247,.3);
  color: #d8b4fe;
}

@media (prefers-reduced-motion: reduce) {
  .ga-09__text { animation: none; background-position: 0% 50%; }
}
(function() {
  const w = document.querySelector('.ga-09');
  w.querySelectorAll('.ga-09__ctrl-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      w.querySelectorAll('.ga-09__ctrl-btn').forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      w.style.setProperty('--dur', btn.dataset.dur);
    });
  });
})();

How this works

The core technique requires three CSS declarations working together: background-image: linear-gradient(...) sets the colour palette, background-size: 300% 100% makes the gradient canvas three times wider than the text element, and the combination of background-clip: text plus -webkit-text-fill-color: transparent punches through the text shape to reveal the gradient beneath. A single shared @keyframes ga-09-slide moves background-position from 0% to -200%, which shifts the visible gradient window across the oversized canvas — because the canvas is 300% wide but the window is 100%, the journey covers two full gradient repetitions, creating a seamless infinite loop.

Different demo variants use different animation-duration values: the large headline runs at 1.4× the base --dur, while stat numbers use staggered animation-delay of 0s, -.8s, and -1.6s so they appear to be at different phases of the same cycle — giving the impression of independent living numbers without multiple keyframe declarations.

Customize

  • Change the colour journey by editing the gradient stops in background-image — repeating the first colour at the end (e.g. ...#a855f7, #a855f7) ensures a seamless loop with no colour jump at the boundary.
  • Control the flow speed globally via --dur on .ga-09 — the JS speed control adjusts this single property, and all text variants scale their own durations via cascade.
  • Use the gradient text technique on a single word within a sentence by wrapping it in a span with display: inline; background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent and the gradient background.
  • Increase contrast on mid-brightness backgrounds by adding text-shadow: 0 0 30px rgba(168,85,247,.3) — note that text-shadow works on -webkit-text-fill-color: transparent elements in all browsers, adding a soft glow behind the clipped gradient.
  • For a static gradient text (no animation) simply remove the animation property and set a fixed background-position: 0% 50% — this gives a clean diagonal gradient title without any motion.

Watch out for

  • -webkit-text-fill-color: transparent is the property that actually makes text transparent to reveal the clipped background — color: transparent alone does not work in WebKit/Blink; always include both color: transparent (as fallback) and -webkit-text-fill-color: transparent.
  • Text selection highlighting becomes invisible when -webkit-text-fill-color: transparent is set in some browsers — always add a scoped ::selection { background: rgba(...); color: #fff } rule so selected text remains legible.
  • SVG text does not support the background-clip: text technique — if you need gradient text inside an SVG, use a linearGradient fill on the text element instead.

Browser support

ChromeSafariFirefoxEdge
57+ 8+ 110+ 57+

background-clip: text is stable everywhere; -webkit-text-fill-color: transparent has been supported in WebKit/Blink since 2014. Firefox added full support in v110 (2023); prior versions need the -webkit- prefix version.

Search CodeFronts

Loading…