25 CSS Text Animations 17 / 25
CSS Liquid Fill Text Animation
Text appears to fill with liquid from bottom to top using a moving gradient mask and clip-path — a striking progress-indicator text technique.
The code
<div class="ta-17">
<div class="ta-17__stage">
<div class="ta-17__container">
<h2 class="ta-17__outline">FILL</h2>
<h2 class="ta-17__liquid">FILL</h2>
</div>
<p class="ta-17__sub">clip-path inset · -webkit-text-stroke · liquid rise</p>
</div>
</div> <div class="ta-17">
<div class="ta-17__stage">
<div class="ta-17__container">
<h2 class="ta-17__outline">FILL</h2>
<h2 class="ta-17__liquid">FILL</h2>
</div>
<p class="ta-17__sub">clip-path inset · -webkit-text-stroke · liquid rise</p>
</div>
</div>.ta-17, .ta-17 *, .ta-17 *::before, .ta-17 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ta-17 ::selection { background: #0369a1; color: #e0f2fe; }
.ta-17 {
--bg: #020c18;
--water: #0ea5e9;
--deep: #0369a1;
min-height: 100vh;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
font-family: 'Syne', 'Helvetica Neue', sans-serif;
}
.ta-17__stage { text-align: center; }
.ta-17__container {
position: relative;
display: inline-block;
line-height: 1;
}
.ta-17__outline,
.ta-17__liquid {
font-size: clamp(4rem, 14vw, 8rem);
font-weight: 900;
letter-spacing: 0.08em;
margin: 0;
line-height: 1;
}
.ta-17__outline {
-webkit-text-stroke: 2px rgba(14, 165, 233, 0.35);
color: transparent;
}
.ta-17__liquid {
position: absolute;
top: 0; left: 0;
background: linear-gradient(to top, var(--deep) 0%, var(--water) 60%, #7dd3fc 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
clip-path: inset(100% 0 0 0);
animation: ta-17-rise 2.4s cubic-bezier(0.4, 0, 0.2, 1) 0.5s forwards;
}
@keyframes ta-17-rise {
to { clip-path: inset(0% 0 0 0); }
}
.ta-17__sub {
font-size: 0.65rem;
color: #0c2a40;
margin-top: 0.6rem;
letter-spacing: 0.1em;
font-family: 'Courier New', monospace;
}
@media (prefers-reduced-motion: reduce) {
.ta-17__liquid { animation: none; clip-path: inset(0% 0 0 0); }
} .ta-17, .ta-17 *, .ta-17 *::before, .ta-17 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ta-17 ::selection { background: #0369a1; color: #e0f2fe; }
.ta-17 {
--bg: #020c18;
--water: #0ea5e9;
--deep: #0369a1;
min-height: 100vh;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
font-family: 'Syne', 'Helvetica Neue', sans-serif;
}
.ta-17__stage { text-align: center; }
.ta-17__container {
position: relative;
display: inline-block;
line-height: 1;
}
.ta-17__outline,
.ta-17__liquid {
font-size: clamp(4rem, 14vw, 8rem);
font-weight: 900;
letter-spacing: 0.08em;
margin: 0;
line-height: 1;
}
.ta-17__outline {
-webkit-text-stroke: 2px rgba(14, 165, 233, 0.35);
color: transparent;
}
.ta-17__liquid {
position: absolute;
top: 0; left: 0;
background: linear-gradient(to top, var(--deep) 0%, var(--water) 60%, #7dd3fc 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
clip-path: inset(100% 0 0 0);
animation: ta-17-rise 2.4s cubic-bezier(0.4, 0, 0.2, 1) 0.5s forwards;
}
@keyframes ta-17-rise {
to { clip-path: inset(0% 0 0 0); }
}
.ta-17__sub {
font-size: 0.65rem;
color: #0c2a40;
margin-top: 0.6rem;
letter-spacing: 0.1em;
font-family: 'Courier New', monospace;
}
@media (prefers-reduced-motion: reduce) {
.ta-17__liquid { animation: none; clip-path: inset(0% 0 0 0); }
}How this works
The technique layers two copies of the text — an unfilled outline using -webkit-text-stroke as the base, and a filled solid colour version clipped with clip-path: inset() that starts at the bottom edge and animates upward. The clip-path: inset(100% 0 0 0) value clips from the top, hiding the filled text entirely. Animating it to inset(0% 0 0 0) progressively reveals the filled layer from bottom to top over the stroke outline.
A subtle wave distortion on the liquid surface is added by animating a pseudo-element with a radial-gradient ellipse that moves sinusoidally across the top edge of the fill, creating the meniscus wobble of a rising liquid surface. The liquid colour uses a two-tone gradient — darker at the bottom, brighter at the surface — reinforcing the depth cue of a column of coloured fluid.
Customize
- Change the fill speed by adjusting
animation-durationon the::afterclipped text —2sis dramatic,6sis slow and satisfying. - Change the fill direction from bottom-to-top to left-to-right by using
inset(0 100% 0 0)as the start state andinset(0 0 0 0)as the end. - Combine with a percentage counter displayed above the text to create a loading or skill-level indicator where the fill percentage is data-driven via JS.
- Change the liquid colour from a blue water effect to lava by using a gradient of
#ef4444to#f97316with an orange glow on the surface wave. - Loop the animation with a drain phase — adding a second keyframe section that refills from 0% to 100% — for an infinite animated fill-and-drain cycle.
Watch out for
-webkit-text-strokehas no non-prefixed version in the CSS spec as of 2025 — it's a WebKit extension that works in Chrome, Safari, and Edge but is not in Firefox's standard implementation.- The clip-path approach requires the filled copy to be
position: absolutelayered exactly over the stroke text — any font-rendering subpixel difference between the two can cause a visible double-image. - Gradient fills inside clipped text can produce aliasing at the clip boundary in Safari — adding
-webkit-font-smoothing: antialiasedand a very slighttransform: translateZ(0)mitigates this.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 55+ | 10.1+ | 71+ | 55+ |
-webkit-text-stroke is widely supported experimentally. Firefox renders the stroke but may differ in exact weight; always test cross-browser.