Wave Crest
Elemental & Nature · Animated · pure CSS
Water fills the letters with a real rolling surf: the level holds at mid-height while a shaped sine crest travels sideways as an SVG wave mask, topped by a bright foam line riding the exact same curve. Below the crest a deep aqua gradient fills the glyphs and above it they read as a hollow stroked outline, with a gentle vertical bob making the whole body swell.
How it works
Wave Crest is an animated elemental & nature text effect rendered entirely in CSS. A data-text attribute mirrors the word into ::before/::after layers, so copy that attribute together with the CSS.
Controls
Wave Crest exposes 3 dedicated controls — Water Hue, Wave Height and Travel Speed — on top of the shared type controls (font, weight, letter-spacing and case). Open it in the generator to tune every value live, then copy the updated CSS.
CSS
/* Wave Crest — made with TEXT-FX · https://text-fx.app
* HTML: the element needs a data-text attribute equal to its text.
* Font: 'Anton', sans-serif (load from Google Fonts).
*/
.text-effect {
font-family: 'Anton', sans-serif;
font-weight: 700;
letter-spacing: 2px;
text-transform: none;
}
.text-effect {
position: relative;
-webkit-text-stroke: 1.5px hsl(205 48% 72% / 0.9);
color: transparent;
-webkit-text-fill-color: transparent;
}
.text-effect::before, .text-effect::after {
content: attr(data-text);
position: absolute;
inset: 0;
pointer-events: none;
animation: text-effect-travel 3.3s linear infinite, text-effect-bob 4.95s ease-in-out infinite;
}
.text-effect::before {
background: linear-gradient(to bottom, hsl(205 70% 50%) 0%, hsl(215 78% 30%) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22none%22%3E%3Cpath%20d%3D%22M0%2C50%20Q25%2C41%2050%2C50%20T100%2C50%20L100%2C100%20L0%2C100%20Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E");
mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22none%22%3E%3Cpath%20d%3D%22M0%2C50%20Q25%2C41%2050%2C50%20T100%2C50%20L100%2C100%20L0%2C100%20Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E");
-webkit-mask-repeat: repeat-x;
mask-repeat: repeat-x;
-webkit-mask-size: 1.08em 100%;
mask-size: 1.08em 100%;
-webkit-mask-position: 0 0;
mask-position: 0 0;
}
.text-effect::after {
color: hsl(205 60% 90%);
-webkit-text-fill-color: hsl(205 60% 90%);
-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22none%22%3E%3Cpath%20d%3D%22M0%2C50%20Q25%2C41%2050%2C50%20T100%2C50%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%225.9%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E");
mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22none%22%3E%3Cpath%20d%3D%22M0%2C50%20Q25%2C41%2050%2C50%20T100%2C50%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%225.9%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E");
-webkit-mask-repeat: repeat-x;
mask-repeat: repeat-x;
-webkit-mask-size: 1.08em 100%;
mask-size: 1.08em 100%;
-webkit-mask-position: 0 0;
mask-position: 0 0;
filter: drop-shadow(0 0 3px hsl(205 90% 80% / 0.6)) drop-shadow(0 0 6px hsl(205 90% 80% / 0.6));
}
@keyframes text-effect-travel {
from { -webkit-mask-position: 0 0; mask-position: 0 0; }
to { -webkit-mask-position: -1.08em 0; mask-position: -1.08em 0; }
}
@keyframes text-effect-bob {
0%, 100% { transform: translateY(0.045em); }
50% { transform: translateY(-0.045em); }
}
HTML
This effect needs the markup below (per-letter spans, SVG defs, or a data-text attribute).
<!-- Made with TEXT-FX · https://text-fx.app -->
<style>
.text-effect {
font-family: 'Anton', sans-serif;
font-weight: 700;
letter-spacing: 2px;
text-transform: none;
}
.text-effect {
position: relative;
-webkit-text-stroke: 1.5px hsl(205 48% 72% / 0.9);
color: transparent;
-webkit-text-fill-color: transparent;
}
.text-effect::before, .text-effect::after {
content: attr(data-text);
position: absolute;
inset: 0;
pointer-events: none;
animation: text-effect-travel 3.3s linear infinite, text-effect-bob 4.95s ease-in-out infinite;
}
.text-effect::before {
background: linear-gradient(to bottom, hsl(205 70% 50%) 0%, hsl(215 78% 30%) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22none%22%3E%3Cpath%20d%3D%22M0%2C50%20Q25%2C41%2050%2C50%20T100%2C50%20L100%2C100%20L0%2C100%20Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E");
mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22none%22%3E%3Cpath%20d%3D%22M0%2C50%20Q25%2C41%2050%2C50%20T100%2C50%20L100%2C100%20L0%2C100%20Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E");
-webkit-mask-repeat: repeat-x;
mask-repeat: repeat-x;
-webkit-mask-size: 1.08em 100%;
mask-size: 1.08em 100%;
-webkit-mask-position: 0 0;
mask-position: 0 0;
}
.text-effect::after {
color: hsl(205 60% 90%);
-webkit-text-fill-color: hsl(205 60% 90%);
-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22none%22%3E%3Cpath%20d%3D%22M0%2C50%20Q25%2C41%2050%2C50%20T100%2C50%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%225.9%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E");
mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22none%22%3E%3Cpath%20d%3D%22M0%2C50%20Q25%2C41%2050%2C50%20T100%2C50%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%225.9%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E");
-webkit-mask-repeat: repeat-x;
mask-repeat: repeat-x;
-webkit-mask-size: 1.08em 100%;
mask-size: 1.08em 100%;
-webkit-mask-position: 0 0;
mask-position: 0 0;
filter: drop-shadow(0 0 3px hsl(205 90% 80% / 0.6)) drop-shadow(0 0 6px hsl(205 90% 80% / 0.6));
}
@keyframes text-effect-travel {
from { -webkit-mask-position: 0 0; mask-position: 0 0; }
to { -webkit-mask-position: -1.08em 0; mask-position: -1.08em 0; }
}
@keyframes text-effect-bob {
0%, 100% { transform: translateY(0.045em); }
50% { transform: translateY(-0.045em); }
}
</style>
<div data-text="Your text" class="text-effect">Your text</div>
- Category
- Elemental & Nature
- Type
- Animated
- Browser support
- background-clip:text + an SVG wave mask travelled via mask-position
- Capabilities
- dataText