26 CSS Link Effects05 / 26

Pure CSSMIT licensed

CSS Text Fill & Color Wave Hover Effect

Liquid typography: ink that rises inside the glyphs when you hover. A flat fill that climbs from the baseline, a wavy crest version that actually looks like liquid (an inline SVG wave, no canvas), and a color wave that flows through the letters for as long as the pointer stays. All three are one gradient trick wearing different costumes.

Published

Live Demo
Try it

The code

<section class="cle-05" aria-label="Text fill and color wave hover demo">
  <div class="cle-05__stage">
    <div class="cle-05__var"><a href="#a" class="cle-05__rise">Reservoir</a><small class="cle-05__cap">A · flat fill rises</small></div>
    <div class="cle-05__var"><a href="#b" class="cle-05__wave">High tide</a><small class="cle-05__cap">B · liquid crest (SVG wave)</small></div>
    <div class="cle-05__var"><a href="#c" class="cle-05__flow">Spectrum</a><small class="cle-05__cap">C · color wave while hovered</small></div>
  </div>
</section>
.cle-05,
.cle-05 *,
.cle-05 *::before,
.cle-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cle-05 {
  width: 100%;
  min-height: 100vh;
  --ink: oklch(0.26 0.02 240);
  --brand: oklch(0.58 0.14 230);
  font-family: 'Segoe UI',system-ui,sans-serif;
}

.cle-05__stage {
  width: 100%;
  height: 100vh;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 42px;
  border: 1px solid oklch(0.9 0.012 240);
  background: linear-gradient(180deg,oklch(0.985 0.005 220),oklch(0.945 0.015 230));
  padding: 44px 32px;
}

.cle-05__var {
  display: grid;
  gap: 12px;
  justify-items: center;
}

.cle-05__cap {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: oklch(0.55 0.03 235);
}

.cle-05 a {
  font-size: clamp(34px,5.6vw,52px);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1.1;
  text-decoration: none;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  background-repeat: repeat-x;
}

.cle-05 a::selection {
  background: oklch(0.85 0.06 230 / .5);
  color: var(--ink);
  -webkit-text-fill-color: var(--ink);
}

.cle-05 a:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 6px;
  border-radius: 4px;
}

.cle-05__rise {
  background-image: linear-gradient(to top,var(--brand) 0 50%,var(--ink) 50% 100%);
  background-size: 100% 200%;
  background-position: 0 0;
  transition: background-position .55s cubic-bezier(.3,.7,.3,1);
}

.cle-05__rise:hover,
.cle-05__rise:focus-visible {
  background-position: 0 100%;
}

.cle-05__wave {
  background-color: var(--ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 90 100' preserveAspectRatio='none'%3E%3Cpath d='M0 51 Q11.25 44 22.5 51 T45 51 T67.5 51 T90 51 V101 H0 Z' fill='%230284c7'/%3E%3C/svg%3E");
  background-size: 90px 200%;
  background-position: 0 0;
  transition: background-position .8s cubic-bezier(.34,1.3,.4,1);
}

.cle-05__wave:hover,
.cle-05__wave:focus-visible {
  background-position: 0 100%;
}

.cle-05__flow {
  background-image: repeating-linear-gradient(100deg,oklch(0.58 0.14 230) 0,oklch(0.6 0.19 300) 70px,oklch(0.72 0.17 60) 140px,oklch(0.58 0.14 230) 210px);
  background-size: 210px 100%;
  animation: clh05-flow 2.2s linear infinite paused;
}

.cle-05__flow:hover,
.cle-05__flow:focus-visible {
  animation-play-state: running;
}

@keyframes clh05-flow {
  to {
    background-position: -210px 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cle-05 * {
    transition-duration: .01s !important;
    animation: none !important;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Link 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 Text Fill & Color Wave Hover Effect
Source: https://codefronts.com/motion/css-link-effects/css-text-fill-and-color-wave-hover-effect/

Liquid typography: ink that rises inside the glyphs when you hover. A flat fill that climbs from the baseline, a wavy crest version that actually looks like liquid (an inline SVG wave, no canvas), and a color wave that flows through the letters for as long as the pointer stays. All three are one gradient trick wearing different costumes.
## HTML
```html
<section class="cle-05" aria-label="Text fill and color wave hover demo">
  <div class="cle-05__stage">
    <div class="cle-05__var"><a href="#a" class="cle-05__rise">Reservoir</a><small class="cle-05__cap">A · flat fill rises</small></div>
    <div class="cle-05__var"><a href="#b" class="cle-05__wave">High tide</a><small class="cle-05__cap">B · liquid crest (SVG wave)</small></div>
    <div class="cle-05__var"><a href="#c" class="cle-05__flow">Spectrum</a><small class="cle-05__cap">C · color wave while hovered</small></div>
  </div>
</section>
```
## CSS
```css
.cle-05,
.cle-05 *,
.cle-05 *::before,
.cle-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cle-05 {
  width: 100%;
  min-height: 100vh;
  --ink: oklch(0.26 0.02 240);
  --brand: oklch(0.58 0.14 230);
  font-family: 'Segoe UI',system-ui,sans-serif;
}

.cle-05__stage {
  width: 100%;
  height: 100vh;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 42px;
  border: 1px solid oklch(0.9 0.012 240);
  background: linear-gradient(180deg,oklch(0.985 0.005 220),oklch(0.945 0.015 230));
  padding: 44px 32px;
}

.cle-05__var {
  display: grid;
  gap: 12px;
  justify-items: center;
}

.cle-05__cap {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: oklch(0.55 0.03 235);
}

.cle-05 a {
  font-size: clamp(34px,5.6vw,52px);
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1.1;
  text-decoration: none;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  background-repeat: repeat-x;
}

.cle-05 a::selection {
  background: oklch(0.85 0.06 230 / .5);
  color: var(--ink);
  -webkit-text-fill-color: var(--ink);
}

.cle-05 a:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 6px;
  border-radius: 4px;
}

.cle-05__rise {
  background-image: linear-gradient(to top,var(--brand) 0 50%,var(--ink) 50% 100%);
  background-size: 100% 200%;
  background-position: 0 0;
  transition: background-position .55s cubic-bezier(.3,.7,.3,1);
}

.cle-05__rise:hover,
.cle-05__rise:focus-visible {
  background-position: 0 100%;
}

.cle-05__wave {
  background-color: var(--ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 90 100' preserveAspectRatio='none'%3E%3Cpath d='M0 51 Q11.25 44 22.5 51 T45 51 T67.5 51 T90 51 V101 H0 Z' fill='%230284c7'/%3E%3C/svg%3E");
  background-size: 90px 200%;
  background-position: 0 0;
  transition: background-position .8s cubic-bezier(.34,1.3,.4,1);
}

.cle-05__wave:hover,
.cle-05__wave:focus-visible {
  background-position: 0 100%;
}

.cle-05__flow {
  background-image: repeating-linear-gradient(100deg,oklch(0.58 0.14 230) 0,oklch(0.6 0.19 300) 70px,oklch(0.72 0.17 60) 140px,oklch(0.58 0.14 230) 210px);
  background-size: 210px 100%;
  animation: clh05-flow 2.2s linear infinite paused;
}

.cle-05__flow:hover,
.cle-05__flow:focus-visible {
  animation-play-state: running;
}

@keyframes clh05-flow {
  to {
    background-position: -210px 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cle-05 * {
    transition-duration: .01s !important;
    animation: none !important;
  }
}
```

How this works

Clip the background to the glyphs, make the background twice the element's height, and slide the viewing window with background-position:

.rise{
  background-image: linear-gradient(to top, var(--brand) 50%, var(--ink) 50%);
  background-size: 100% 200%;   /* twice as tall as the text */
  background-position: 0 0;      /* window shows the top (ink) half */
  -webkit-background-clip: text; background-clip: text; color: transparent;
  transition: background-position .55s cubic-bezier(.3,.7,.3,1);
}
.rise:hover{ background-position: 0 100%; } /* window slides to the brand half */

Because the image is 200% tall, moving the position from 0 to 100% pans exactly one text-height — the color boundary sweeps up through the letters. Variant B swaps the flat gradient for a repeating SVG data-URI whose top edge is a drawn wave (preserveAspectRatio="none" lets it stretch to the 200% box), so the rising boundary has a liquid crest. C paints a seamless repeating-linear-gradient and scrolls it horizontally in a keyframe loop that idles at animation-play-state: paused and runs on hover — pausing instead of removing the animation is what prevents a snap when the pointer leaves.

Make it yours

  • Fill color: variant A/C tokens are plain CSS vars; B's crest color is hardcoded in the SVG data-URI (fill='%230284c7') — keep it in sync with --brand.
  • Fill speed: the .55s transition; give B a heavier, liquid feel at .8s with the same overshoot curve.
  • Wave frequency in B: the 90px horizontal background-size tile — smaller tile, choppier water.
  • C's flow speed is the 2.2s loop; reverse direction with animation-direction: reverse.

Gotchas — read before shipping

  • background-clip:text needs the -webkit- prefix pair for Safari, and color:transparent (or -webkit-text-fill-color) to reveal the paint.
  • The 200%-height trick assumes a single line: on wrapped text the boundary crosses both lines at once, which reads fine for A/C but breaks B's waterline illusion — keep B on one-line display links.
  • Set an explicit ::selection style; transparent text with default selection colors can vanish when highlighted.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

background-clip:text is Baseline everywhere (prefixed in WebKit); SVG data-URI backgrounds are universal. oklch/color-mix set the stated floor — swap to hex for legacy reach.

Search CodeFronts

Loading…