20 CSS Text Gradient Effects 05 / 20

CSS Hover Transition Text Gradient Effect

A navigation and card component that transitions solid text into a gradient on hover using -webkit-text-fill-color transition.

Pure CSS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="tg-05">
  <div class="tg-05__card">
    <p class="tg-05__hint">Hover over any link</p>
    <nav class="tg-05__nav">
      <a href="#" class="tg-05__link">Home</a>
      <a href="#" class="tg-05__link">Features</a>
      <a href="#" class="tg-05__link">Pricing</a>
      <a href="#" class="tg-05__link">Blog</a>
      <a href="#" class="tg-05__link">Contact</a>
    </nav>
    <div class="tg-05__divider"></div>
    <div class="tg-05__cards">
      <div class="tg-05__tile">
        <h3 class="tg-05__tile-h">Analytics <span class="tg-05__arrow">→</span></h3>
        <p>Real-time dashboards for every metric your team cares about.</p>
      </div>
      <div class="tg-05__tile">
        <h3 class="tg-05__tile-h">Integrations <span class="tg-05__arrow">→</span></h3>
        <p>Connect to 200+ tools with one-click OAuth flows.</p>
      </div>
      <div class="tg-05__tile">
        <h3 class="tg-05__tile-h">Collaboration <span class="tg-05__arrow">→</span></h3>
        <p>Shared workspaces with granular permission controls.</p>
      </div>
    </div>
  </div>
</div>
.tg-05, .tg-05 *, .tg-05 *::before, .tg-05 *::after { margin:0; padding:0; box-sizing:border-box; }
.tg-05 ::selection { background:#7c3aed; color:#fff; }

.tg-05 {
  --g-a: #7c3aed;
  --g-b: #ec4899;
  --bg: #0f0f14;
  --surface: #17171f;
  --text: #e2e8f0;
  --muted: #64748b;
  --solid: #94a3b8;
  --border: rgba(255,255,255,.07);
  font-family: system-ui, -apple-system, sans-serif;
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 24px;
}

.tg-05__card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 36px 32px;
  width: 100%;
  max-width: 580px;
}

.tg-05__hint {
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .1em;
  color: var(--muted);
  margin-bottom: 20px;
}

.tg-05__nav {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-bottom: 28px;
}

/* 
  Hover gradient technique:
  Gradient sits on ::before; opacity transitions from 0→1.
  -webkit-text-fill-color can't transition, so we use a pseudo-element
  trick: normal color fades out, gradient fades in via opacity on ::before.
  We achieve the gradient-on-hover by toggling background-clip on the element itself.
*/
.tg-05__link {
  position: relative;
  text-decoration: none;
  font-size: .95rem;
  font-weight: 600;
  padding: 8px 18px;
  border-radius: 8px;
  color: var(--solid);
  transition: color .3s;
  display: inline-block;
}

.tg-05__link::before {
  content: attr(data-hover, '');
  position: absolute;
  inset: 0;
  padding: 8px 18px;
  background: linear-gradient(90deg, var(--g-a), var(--g-b));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  opacity: 0;
  transition: opacity .3s;
  pointer-events: none;
}
.tg-05__link::after {
  content: attr(href, '');
  position: absolute;
  inset: 8px 18px;
  background: linear-gradient(90deg, var(--g-a), var(--g-b));
  -webkit-background-clip: text;
  background-clip: text;
  opacity: 0;
  transition: opacity .3s;
}

/* Simpler approach: fade out the solid text colour, overlay gradient clone */
.tg-05__link span { /* We'll use a direct CSS trick on the element */ }

.tg-05__link:hover {
  background: linear-gradient(90deg, var(--g-a), var(--g-b));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
/* Smooth fade: CSS can't tween background-clip text, so we fade background colour instead */
.tg-05__link {
  background: linear-gradient(90deg, var(--g-a), var(--g-b));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: var(--solid);
  transition: -webkit-text-fill-color .25s;
}
.tg-05__link:hover {
  -webkit-text-fill-color: transparent;
}

.tg-05__divider { height: 1px; background: var(--border); margin-bottom: 28px; }

.tg-05__cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 14px;
}

.tg-05__tile {
  background: rgba(255,255,255,.03);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 18px;
}
.tg-05__tile p { font-size: .8rem; color: var(--muted); line-height: 1.55; margin-top: 6px; }

.tg-05__tile-h {
  font-size: .9375rem;
  font-weight: 700;
  color: var(--text);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.tg-05__arrow { color: var(--muted); font-weight: 400; }
.tg-05__tile:hover .tg-05__tile-h {
  background: linear-gradient(90deg, var(--g-a), var(--g-b));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .tg-05__link, .tg-05__tile { transition: none; }
}

How this works

Hover gradient text faces a fundamental constraint: -webkit-text-fill-color and background-clip cannot be transitioned by the browser. The workaround is to pre-apply the gradient in the default state but set -webkit-text-fill-color to the solid colour. On hover, it transitions to transparent, revealing the pre-loaded gradient beneath — this single property is transitionable.

The card titles use a CSS hover selector on the parent .tg-05__tile to apply background: linear-gradient; -webkit-background-clip: text; -webkit-text-fill-color: transparent to the heading child. This pattern works because descendant selectors inside :hover rules recompute instantly on pointer entry, with no JS event needed.

Customize

  • Customise the gradient end colours by editing --g-a and --g-b on .tg-05 — the transition duration on -webkit-text-fill-color controls how fast the fade feels.
  • Increase or decrease hover speed by editing the .25s value in transition: -webkit-text-fill-color .25s — values below .15s feel snappy, above .4s feel luxurious.
  • Add an underline indicator by using a ::after pseudo-element with height: 2px; background: linear-gradient(…); transform: scaleX(0); transition: transform .3s scaling to 1 on hover.
  • Apply the same gradient-reveal pattern to heading elements by wrapping text in a span and toggling the fill colour via a parent container's :hover state.
  • Combine with a background tint by adding background: rgba(var(--g-a-rgb), .08) on .tg-05__link:hover for a pill highlight effect behind the gradient text.

Watch out for

  • The hover effect uses -webkit-text-fill-color transitioning from a solid colour to transparent. However, transparent in this context means the gradient background shows through — on Firefox, this transition behaves slightly differently if the gradient background-size or position changes between states.
  • Don't set -webkit-text-fill-color in a media query without also setting it in the base state — some preprocessors strip webkit-prefixed properties from media queries in optimisation passes, causing the fill colour to remain visible on hover.
  • Card hover states that change -webkit-background-clip can cause a brief flash in Safari during the first hover — this is a paint invalidation issue. Mitigate by pre-applying the gradient and only transitioning -webkit-text-fill-color as demonstrated in this demo.

Browser support

ChromeSafariFirefoxEdge
69+ 12.1+ 83+ 69+

Transitioning -webkit-text-fill-color from a colour to transparent is reliable across all modern engines; avoid transitioning it alongside background-position simultaneously.

Search CodeFronts

Loading…