Skip to content
· 11 min read · 0 views

CSS Text Animations — 10 Effects You Can Copy-Paste Today

Learn how to build 10 stunning CSS text animations from scratch: glitch, typewriter, gradient flow, wave bounce, blur reveal, neon glow, retro flicker, text scramble, split reveal, and stroke draw. Full code included.

// table of contents (21 sections)

Try them live — All 10 animations are available on CSSKit where you can customize parameters and copy the code. Source code on GitHub.


CSS text animations are one of the easiest ways to make a page feel alive. No JavaScript, no libraries — just pure CSS. In this post, I’ll walk through 10 text animation techniques, each one production-ready and customizable through CSS custom properties.

1. Glitch Text

A color-split glitch effect using ::before and ::after pseudo-elements with clip-path.

How It Works

The trick is creating two copies of the text with ::before and ::after, each clipped to a horizontal slice using clip-path: polygon(). The red copy shows the top third, cyan shows the bottom third. Both animate with small translate offsets at different timings, creating the glitch displacement.

.text-glitch {
  --glitch-color: #ff0040;
  --glitch-speed: 2s;
  --glitch-text: "GLITCH";
  font-size: 3rem;
  font-weight: 900;
  color: white;
  position: relative;
  display: inline-block;
}

/* Two glitch layers — top and bottom slices */
.text-glitch::before,
.text-glitch::after {
  content: var(--glitch-text);
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* Red layer — top 35% */
.text-glitch::before {
  color: var(--glitch-color);
  animation: glitch-1 var(--glitch-speed) infinite linear alternate-reverse;
  clip-path: polygon(0 0, 100% 0, 100% 35%, 0 35%);
}

/* Cyan layer — bottom 35% */
.text-glitch::after {
  color: cyan;
  animation: glitch-2 var(--glitch-speed) infinite linear alternate-reverse;
  clip-path: polygon(0 65%, 100% 65%, 100% 100%, 0 100%);
}

@keyframes glitch-1 {
  0% { transform: translate(0); }
  20% { transform: translate(-3px, 3px); }
  40% { transform: translate(-3px, -3px); }
  60% { transform: translate(3px, 3px); }
  80% { transform: translate(3px, -3px); }
  100% { transform: translate(0); }
}

@keyframes glitch-2 {
  0% { transform: translate(0); }
  20% { transform: translate(3px, -3px); }
  40% { transform: translate(3px, 3px); }
  60% { transform: translate(-3px, -3px); }
  80% { transform: translate(-3px, 3px); }
  100% { transform: translate(0); }
}
<span class="text-glitch" style="--glitch-text: 'GLITCH'">GLITCH</span>

Customize: Change --glitch-color for the red layer color, --glitch-speed for timing, and --glitch-text for the displayed word.


2. Typewriter

Character-by-character typing with a blinking cursor. Uses steps() for that mechanical feel.

How It Works

Two animations run simultaneously: typing grows the width from 0 to 100% using steps() for a mechanical character-by-character reveal, while blink toggles the right border to create the cursor flash. The overflow: hidden and white-space: nowrap ensure text doesn’t wrap during the reveal.

.typewriter {
  --type-speed: 3s;
  --type-color: #00ff88;
  --type-size: 2rem;
  font-family: monospace;
  font-size: var(--type-size);
  color: var(--type-color);
  overflow: hidden;
  white-space: nowrap;
  border-right: 3px solid var(--type-color);
  width: 0;
  animation:
    typing var(--type-speed) steps(12) forwards,
    blink 0.7s step-end infinite;
}

@keyframes typing {
  from { width: 0; }
  to { width: 100%; }
}

@keyframes blink {
  50% { border-color: transparent; }
}
<div class="typewriter">Hello World!</div>

Key detail: The steps(12) must match the number of characters. For longer text, increase the step count.


3. Gradient Text

Flowing color gradient across text using background-clip: text.

How It Works

The magic is background-clip: text combined with -webkit-text-fill-color: transparent. This makes the text itself act as a mask for the gradient background. By animating background-position across a 300% wide gradient, the colors flow smoothly through the text.

.gradient-text {
  --grad-color-1: #ff6b6b;
  --grad-color-2: #feca57;
  --grad-color-3: #48dbfb;
  --grad-speed: 3s;
  font-size: 3.5rem;
  font-weight: 900;
  background: linear-gradient(
    90deg,
    var(--grad-color-1),
    var(--grad-color-2),
    var(--grad-color-3),
    var(--grad-color-1)
  );
  background-size: 300% 100%;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  animation: gradient-shift var(--grad-speed) ease infinite;
}

@keyframes gradient-shift {
  0% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
<span class="gradient-text">GRADIENT</span>

Tip: The gradient repeats the first color at the end (--grad-color-1 appears twice) to create a seamless loop.


4. Wave Text

Each letter bounces in a wave using staggered animation-delay.

How It Works

Each character is wrapped in its own <span>, and each span gets a slightly different animation-delay (0s, 0.1s, 0.2s, etc.). The animation itself is simple — translateY moves the letter up and back. The staggered delays create the wave ripple effect.

.wave-text {
  --wave-color: #a855f7;
  --wave-speed: 0.6s;
  --wave-amplitude: -20px;
  display: inline-flex;
  font-size: 2rem;
  font-weight: 800;
  color: var(--wave-color);
}

.wave-text span {
  display: inline-block;
  animation: wave-bounce var(--wave-speed) ease-in-out infinite;
}

.wave-text span:nth-child(1) { animation-delay: 0s; }
.wave-text span:nth-child(2) { animation-delay: 0.1s; }
.wave-text span:nth-child(3) { animation-delay: 0.2s; }
.wave-text span:nth-child(4) { animation-delay: 0.3s; }
.wave-text span:nth-child(5) { animation-delay: 0.4s; }

@keyframes wave-bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(var(--wave-amplitude)); }
}
<div class="wave-text">
  <span>W</span><span>A</span><span>V</span><span>E</span><span>!</span>
</div>

Tip: Increase --wave-amplitude for bigger bounces, or decrease --wave-speed for a faster wave.


5. Blur Reveal

Text transitions from blurred to sharp — a clean, modern entrance effect.

How It Works

The simplest animation in this list — just two properties animated together: filter: blur() fades from 20px to 0px while opacity goes from 0 to 1. The ease-out timing makes it feel natural as the text sharpens quickly then settles.

.blur-reveal {
  --blur-color: #ffffff;
  --blur-amount: 20px;
  --blur-speed: 1.5s;
  font-size: 3rem;
  font-weight: 800;
  color: var(--blur-color);
  animation: blur-in var(--blur-speed) ease-out forwards;
}

@keyframes blur-in {
  0% {
    filter: blur(var(--blur-amount));
    opacity: 0;
  }
  100% {
    filter: blur(0px);
    opacity: 1;
  }
}
<span class="blur-reveal">REVEAL</span>

Note: The forwards fill mode keeps the text visible after the animation completes (it’s a one-shot, not infinite).


6. Neon Glow

Pulsing neon sign using layered text-shadow.

How It Works

The neon effect is built by stacking multiple text-shadow layers at increasing blur radii: 7px, 10px, 20px, 40px, 60px. The animation pulses between a dim state (fewer shadows, lower opacity) and the full glow. Using calc() with --neon-intensity keeps everything proportional when you change the intensity parameter.

.neon-glow {
  --neon-color: #0ff;
  --neon-speed: 2s;
  --neon-intensity: 20px;
  font-size: 3rem;
  font-weight: 900;
  color: var(--neon-color);
  text-shadow:
    0 0 7px var(--neon-color),
    0 0 10px var(--neon-color),
    0 0 calc(var(--neon-intensity)) var(--neon-color),
    0 0 calc(var(--neon-intensity) * 2) var(--neon-color),
    0 0 calc(var(--neon-intensity) * 3) var(--neon-color);
  animation: neon-pulse var(--neon-speed) ease-in-out infinite alternate;
}

@keyframes neon-pulse {
  from {
    text-shadow:
      0 0 4px var(--neon-color),
      0 0 8px var(--neon-color),
      0 0 calc(var(--neon-intensity) * 0.5) var(--neon-color);
    opacity: 0.8;
  }
  to {
    text-shadow:
      0 0 7px var(--neon-color),
      0 0 10px var(--neon-color),
      0 0 var(--neon-intensity) var(--neon-color),
      0 0 calc(var(--neon-intensity) * 2) var(--neon-color),
      0 0 calc(var(--neon-intensity) * 3) var(--neon-color);
    opacity: 1;
  }
}
<span class="neon-glow">NEON</span>

Tip: Dark backgrounds are essential for neon effects to look convincing.


7. Retro Flicker

A neon sign flicker with random-feeling opacity drops — pure CSS, no JavaScript randomness needed.

How It Works

The “randomness” is faked by picking specific percentages in the keyframes where the opacity drops to 0.4 and the text-shadow disappears: 20%, 24%, and 55%. Because these are clustered together but not evenly spaced, the human eye perceives it as unpredictable flickering.

.text-retro {
  --tr-color: #fbbf24;
  --tr-speed: 3s;
  font-family: 'Courier New', monospace;
  font-size: 36px;
  font-weight: 700;
  color: var(--tr-color);
  text-shadow: 0 0 10px var(--tr-color), 0 0 20px var(--tr-color), 0 0 40px var(--tr-color);
  animation: flicker var(--tr-speed) infinite;
  letter-spacing: 4px;
  text-transform: uppercase;
}

@keyframes flicker {
  0%, 19%, 21%, 23%, 25%, 54%, 56%, 100% {
    opacity: 1;
    text-shadow: 0 0 10px var(--tr-color), 0 0 20px var(--tr-color), 0 0 40px var(--tr-color);
  }
  20%, 24%, 55% {
    opacity: 0.4;
    text-shadow: none;
  }
}
<div class="text-retro">Open</div>

8. Text Scramble

Characters cycle through random symbols before resolving — a hacker/decode aesthetic.

How It Works

Each character gets its own <span> with a staggered animation-delay. The @keyframes cycle through symbols (!, @, #, &, %, ?, $) with increasing opacity, creating the decode reveal. The steps(1) timing function ensures each symbol shows discretely without smooth transitions between them.

.text-scramble {
  --tsc-color: #22c55e;
  --tsc-speed: 2s;
  font-family: 'JetBrains Mono', 'Courier New', monospace;
  font-size: 28px;
  font-weight: 700;
  color: var(--tsc-color);
  text-shadow: 0 0 8px var(--tsc-color);
  display: inline-block;
}

.text-scramble span {
  display: inline-block;
  animation: scramble-char var(--tsc-speed) steps(1) both;
}

.text-scramble span:nth-child(1) { animation-delay: 0s; }
.text-scramble span:nth-child(2) { animation-delay: 0.15s; }
.text-scramble span:nth-child(3) { animation-delay: 0.3s; }
.text-scramble span:nth-child(4) { animation-delay: 0.45s; }
.text-scramble span:nth-child(5) { animation-delay: 0.6s; }
.text-scramble span:nth-child(6) { animation-delay: 0.75s; }

@keyframes scramble-char {
  0% { opacity: 0.3; }
  14% { opacity: 0.4; }
  28% { opacity: 0.5; }
  42% { opacity: 0.6; }
  57% { opacity: 0.7; }
  71% { opacity: 0.8; }
  85% { opacity: 0.9; }
  100% { opacity: 1; }
}
<div class="text-scramble"><span>D</span><span>E</span><span>C</span><span>O</span><span>D</span><span>E</span></div>

9. Text Split

Characters split apart horizontally then reassemble — a dramatic reveal.

How It Works

Each character animates through 4 stages: invisible → split apart (translateX + scaleX) → overshoot back → settle. The scaleX(0.3) at 20% stretches the character thin while translateX(30px) pushes it sideways. A brief color flash via var(--ts-accent) adds drama at the split point.

.text-split {
  --ts-color: #e2e8f0;
  --ts-accent: #3b82f6;
  --ts-speed: 1.5s;
  font-size: 32px;
  font-weight: 700;
  display: flex;
  gap: 4px;
}

.text-split-char {
  display: inline-block;
  color: var(--ts-color);
  animation: split-char var(--ts-speed) ease-out both;
}

.text-split-char:nth-child(1) { animation-delay: 0s; }
.text-split-char:nth-child(2) { animation-delay: 0.1s; }
.text-split-char:nth-child(3) { animation-delay: 0.2s; }
.text-split-char:nth-child(4) { animation-delay: 0.3s; }
.text-split-char:nth-child(5) { animation-delay: 0.4s; }
.text-split-char:nth-child(6) { animation-delay: 0.5s; }

@keyframes split-char {
  0% { transform: translateX(0) scaleX(1); opacity: 0; }
  20% { transform: translateX(30px) scaleX(0.3); opacity: 0.5; color: var(--ts-accent); }
  50% { transform: translateX(-10px) scaleX(0.8); opacity: 0.8; }
  100% { transform: translateX(0) scaleX(1); opacity: 1; }
}
<div class="text-split"><span class="text-split-char">S</span><span class="text-split-char">P</span><span class="text-split-char">L</span><span class="text-split-char">I</span><span class="text-split-char">T</span><span class="text-split-char">!</span></div>

10. Stroke Draw

SVG text with an animated stroke that draws itself like handwriting.

How It Works

This uses the SVG stroke-draw technique. The text is an SVG <text> element with fill: none and a stroke. By setting stroke-dasharray and stroke-dashoffset to the same value (the total path length), the stroke is fully hidden. Animating stroke-dashoffset to 0 reveals the stroke progressively, creating the drawing effect.

.text-stroke {
  --tk-color: #3b82f6;
  --tk-speed: 3s;
  --tk-thickness: 2px;
  display: inline-block;
}

.text-stroke svg {
  width: 280px;
  height: 80px;
  overflow: visible;
}

.text-stroke text {
  font-family: 'Space Grotesk', system-ui, sans-serif;
  font-size: 52px;
  font-weight: 700;
  fill: none;
  stroke: var(--tk-color);
  stroke-width: var(--tk-thickness);
  stroke-dasharray: 400;
  stroke-dashoffset: 400;
  animation: stroke-draw var(--tk-speed) ease forwards;
}

@keyframes stroke-draw {
  to { stroke-dashoffset: 0; }
}
<div class="text-stroke">
  <svg viewBox="0 0 280 70">
    <text x="10" y="55">STROKE</text>
  </svg>
</div>

Tip: For best results, measure the actual path length of your text and use that value for stroke-dasharray. You can find it with document.querySelector('text').getTotalLength() in the browser console.


What’s Next?

These 10 effects are just the text category from CSSKit — a free library with 310 animations across 11 categories. Every animation is pure CSS with customizable parameters.

In the next post, we’ll cover hover effects — buttons that fill, borders that draw themselves, and cards that flip in 3D. Follow along on CSSKit or star the repo on GitHub.

You might also like

Enjoyed This Post?

Want to discuss the topic, have questions, or looking to collaborate on something similar? Drop a comment below or reach out directly.

Discussion