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.

Wave Crest

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

Related Elemental & Nature effects