16 CSS Glitch Text Effects03 / 16

Pure CSSMIT licensed

Retro VHS Scanline Glitch

Analog VHS tape aesthetic with drifting transparent scanlines, a slow upward tracking line, chunky serif headline with purple-magenta text-shadow jitter, and an authentic REC indicator.

Published

Live Demo
Try it

The code

<div class="gt-03">
  <div class="gt-03__tape">
    <div class="gt-03__scanlines"></div>
    <div class="gt-03__track"></div>
    <span class="gt-03__timestamp">00:47:22:14</span>
    <div class="gt-03__rec"><div class="gt-03__rec-dot"></div>REC</div>
    <h1 class="gt-03__title">VHS STATIC</h1>
    <span class="gt-03__sub">rewind to remember</span>
    <span class="gt-03__speed">SP · HI-FI · STEREO</span>
  </div>
</div>
.gt-03,
.gt-03 *,
.gt-03 *::before,
.gt-03 *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.gt-03 ::selection {
  background: #b45bd4;
  color: #fff;
}

.gt-03 {
  --magenta: #e040fb;
  --purple: #7b2d8b;
  --light-purple: #ce93d8;
  --tape-bg: #12091a;
  --screen-glow: rgba(180,91,212,0.15);
  min-height: 100vh;
  background: var(--tape-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
  font-family: Georgia, 'Times New Roman', serif;
  position: relative;
  overflow: hidden;
}
/* Ambient glow */

.gt-03::before {
  content: '';
  position: absolute;
  width: 400px;
  height: 200px;
  background: radial-gradient(ellipse, var(--screen-glow) 0%, transparent 70%);
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.gt-03__tape {
  position: relative;
  width: 100%;
  max-width: 480px;
  aspect-ratio: 4/3;
  background: #0d0613;
  border: 2px solid rgba(180,91,212,0.3);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  box-shadow: 0 0 30px rgba(180,91,212,0.2), inset 0 0 40px rgba(0,0,0,0.6);
}
/* Moving scanlines */

.gt-03__scanlines {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
      0deg,
      rgba(0,0,0,0.18) 0px,
      rgba(0,0,0,0.18) 1px,
      transparent 1px,
      transparent 3px
    );
  animation: gt-03-scan-drift 8s linear infinite;
  pointer-events: none;
  z-index: 5;
}

@keyframes gt-03-scan-drift {
  from {
    background-position: 0 0;
  }

  to {
    background-position: 0 60px;
  }
}
/* Tracking line that drifts up */

.gt-03__track {
  position: absolute;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent 0%, rgba(228,64,251,0.6) 20%, rgba(255,255,255,0.4) 50%, rgba(228,64,251,0.6) 80%, transparent 100%);
  filter: blur(1px);
  animation: gt-03-track-move 5s linear infinite;
  z-index: 6;
}

@keyframes gt-03-track-move {
  from {
    top: 110%;
  }

  to {
    top: -10%;
  }
}
/* Corner timestamp */

.gt-03__timestamp {
  position: absolute;
  top: 12px;
  left: 14px;
  font-family: 'Courier New', monospace;
  font-size: 11px;
  color: var(--magenta);
  opacity: 0.7;
  letter-spacing: 1px;
  animation: gt-03-blink 2s steps(1) infinite;
  z-index: 8;
}

@keyframes gt-03-blink {
  0%,
    49% {
    opacity: 0.7;
  }

  50%,
    100% {
    opacity: 0;
  }
}
/* REC badge */

.gt-03__rec {
  position: absolute;
  top: 12px;
  right: 14px;
  font-family: 'Courier New', monospace;
  font-size: 11px;
  color: #ff4444;
  letter-spacing: 2px;
  display: flex;
  align-items: center;
  gap: 5px;
  z-index: 8;
}

.gt-03__rec-dot {
  width: 7px;
  height: 7px;
  background: #ff4444;
  border-radius: 50%;
  animation: gt-03-blink 1s steps(1) infinite;
}
/* Main text */

.gt-03__title {
  font-size: clamp(32px, 6vw, 60px);
  font-weight: 700;
  color: var(--light-purple);
  text-align: center;
  text-shadow: 2px 0 var(--magenta), -2px 0 rgba(0,255,255,0.5), 0 0 20px rgba(180,91,212,0.6);
  position: relative;
  z-index: 4;
  animation: gt-03-jitter 6s steps(1) infinite;
  letter-spacing: 3px;
}
/* Horizontal tracking jitter */

@keyframes gt-03-jitter {
  0%,
    90%,
    100% {
    transform: translateX(0);
    text-shadow: 2px 0 var(--magenta), -2px 0 rgba(0,255,255,0.5), 0 0 20px rgba(180,91,212,0.6);
  }

  92% {
    transform: translateX(-6px);
    text-shadow: 6px 0 var(--magenta), -6px 0 rgba(0,255,255,0.7);
  }

  94% {
    transform: translateX(4px);
    text-shadow: -4px 0 var(--magenta), 4px 0 rgba(0,255,255,0.7);
  }

  96% {
    transform: translateX(-2px);
  }

  98% {
    transform: translateX(0);
  }
}

.gt-03__sub {
  font-family: 'Courier New', monospace;
  font-size: 12px;
  color: rgba(206,147,216,0.5);
  letter-spacing: 5px;
  text-transform: uppercase;
  margin-top: 12px;
  position: relative;
  z-index: 4;
}
/* Tape speed indicator */

.gt-03__speed {
  position: absolute;
  bottom: 10px;
  left: 14px;
  font-family: 'Courier New', monospace;
  font-size: 10px;
  color: rgba(206,147,216,0.4);
  z-index: 8;
  letter-spacing: 1px;
}

@media (prefers-reduced-motion: reduce) {
  .gt-03__scanlines,
    .gt-03__track,
    .gt-03__timestamp,
    .gt-03__rec-dot,
    .gt-03__title {
    animation: none;
  }
}
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 Glitch Text 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: Retro VHS Scanline Glitch
Source: https://codefronts.com/motion/css-glitch-text-effect/retro-vhs-scanline-glitch/

Analog VHS tape aesthetic with drifting transparent scanlines, a slow upward tracking line, chunky serif headline with purple-magenta text-shadow jitter, and an authentic REC indicator.
## HTML
```html
<div class="gt-03">
  <div class="gt-03__tape">
    <div class="gt-03__scanlines"></div>
    <div class="gt-03__track"></div>
    <span class="gt-03__timestamp">00:47:22:14</span>
    <div class="gt-03__rec"><div class="gt-03__rec-dot"></div>REC</div>
    <h1 class="gt-03__title">VHS STATIC</h1>
    <span class="gt-03__sub">rewind to remember</span>
    <span class="gt-03__speed">SP · HI-FI · STEREO</span>
  </div>
</div>
```
## CSS
```css
.gt-03,
.gt-03 *,
.gt-03 *::before,
.gt-03 *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.gt-03 ::selection {
  background: #b45bd4;
  color: #fff;
}

.gt-03 {
  --magenta: #e040fb;
  --purple: #7b2d8b;
  --light-purple: #ce93d8;
  --tape-bg: #12091a;
  --screen-glow: rgba(180,91,212,0.15);
  min-height: 100vh;
  background: var(--tape-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
  font-family: Georgia, 'Times New Roman', serif;
  position: relative;
  overflow: hidden;
}
/* Ambient glow */

.gt-03::before {
  content: '';
  position: absolute;
  width: 400px;
  height: 200px;
  background: radial-gradient(ellipse, var(--screen-glow) 0%, transparent 70%);
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.gt-03__tape {
  position: relative;
  width: 100%;
  max-width: 480px;
  aspect-ratio: 4/3;
  background: #0d0613;
  border: 2px solid rgba(180,91,212,0.3);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  box-shadow: 0 0 30px rgba(180,91,212,0.2), inset 0 0 40px rgba(0,0,0,0.6);
}
/* Moving scanlines */

.gt-03__scanlines {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
      0deg,
      rgba(0,0,0,0.18) 0px,
      rgba(0,0,0,0.18) 1px,
      transparent 1px,
      transparent 3px
    );
  animation: gt-03-scan-drift 8s linear infinite;
  pointer-events: none;
  z-index: 5;
}

@keyframes gt-03-scan-drift {
  from {
    background-position: 0 0;
  }

  to {
    background-position: 0 60px;
  }
}
/* Tracking line that drifts up */

.gt-03__track {
  position: absolute;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent 0%, rgba(228,64,251,0.6) 20%, rgba(255,255,255,0.4) 50%, rgba(228,64,251,0.6) 80%, transparent 100%);
  filter: blur(1px);
  animation: gt-03-track-move 5s linear infinite;
  z-index: 6;
}

@keyframes gt-03-track-move {
  from {
    top: 110%;
  }

  to {
    top: -10%;
  }
}
/* Corner timestamp */

.gt-03__timestamp {
  position: absolute;
  top: 12px;
  left: 14px;
  font-family: 'Courier New', monospace;
  font-size: 11px;
  color: var(--magenta);
  opacity: 0.7;
  letter-spacing: 1px;
  animation: gt-03-blink 2s steps(1) infinite;
  z-index: 8;
}

@keyframes gt-03-blink {
  0%,
    49% {
    opacity: 0.7;
  }

  50%,
    100% {
    opacity: 0;
  }
}
/* REC badge */

.gt-03__rec {
  position: absolute;
  top: 12px;
  right: 14px;
  font-family: 'Courier New', monospace;
  font-size: 11px;
  color: #ff4444;
  letter-spacing: 2px;
  display: flex;
  align-items: center;
  gap: 5px;
  z-index: 8;
}

.gt-03__rec-dot {
  width: 7px;
  height: 7px;
  background: #ff4444;
  border-radius: 50%;
  animation: gt-03-blink 1s steps(1) infinite;
}
/* Main text */

.gt-03__title {
  font-size: clamp(32px, 6vw, 60px);
  font-weight: 700;
  color: var(--light-purple);
  text-align: center;
  text-shadow: 2px 0 var(--magenta), -2px 0 rgba(0,255,255,0.5), 0 0 20px rgba(180,91,212,0.6);
  position: relative;
  z-index: 4;
  animation: gt-03-jitter 6s steps(1) infinite;
  letter-spacing: 3px;
}
/* Horizontal tracking jitter */

@keyframes gt-03-jitter {
  0%,
    90%,
    100% {
    transform: translateX(0);
    text-shadow: 2px 0 var(--magenta), -2px 0 rgba(0,255,255,0.5), 0 0 20px rgba(180,91,212,0.6);
  }

  92% {
    transform: translateX(-6px);
    text-shadow: 6px 0 var(--magenta), -6px 0 rgba(0,255,255,0.7);
  }

  94% {
    transform: translateX(4px);
    text-shadow: -4px 0 var(--magenta), 4px 0 rgba(0,255,255,0.7);
  }

  96% {
    transform: translateX(-2px);
  }

  98% {
    transform: translateX(0);
  }
}

.gt-03__sub {
  font-family: 'Courier New', monospace;
  font-size: 12px;
  color: rgba(206,147,216,0.5);
  letter-spacing: 5px;
  text-transform: uppercase;
  margin-top: 12px;
  position: relative;
  z-index: 4;
}
/* Tape speed indicator */

.gt-03__speed {
  position: absolute;
  bottom: 10px;
  left: 14px;
  font-family: 'Courier New', monospace;
  font-size: 10px;
  color: rgba(206,147,216,0.4);
  z-index: 8;
  letter-spacing: 1px;
}

@media (prefers-reduced-motion: reduce) {
  .gt-03__scanlines,
    .gt-03__track,
    .gt-03__timestamp,
    .gt-03__rec-dot,
    .gt-03__title {
    animation: none;
  }
}
```

How this works

The scanline overlay is a repeating-linear-gradient of semi-transparent stripes applied to an absolutely-positioned child div. Animating its background-position from 0 0 to 0 60px moves the stripe pattern downward continuously, but because the stripes repeat every 3px the visual impression is of lines drifting upward -- a classic VHS tape-head artifact. The tracking bar is a separate element that animates top from 110% to -10% over 5 seconds with linear timing.

The headline jitter uses a steps(1) keyframe that fires at 92%, 94%, and 96% of a 6-second loop, snapping translateX and text-shadow offset values simultaneously. This mimics the way a VHS head losing sync displaces the entire scanline horizontally rather than just the text. The magenta text-shadow with a negative-offset cyan counter-shadow recreates the colour bleed caused by magnetic tape degradation.

Make it yours

  • Thicken the scanlines by increasing the first gradient stop from 1px to 2px -- e.g. rgba(0,0,0,0.2) 2px, transparent 2px, transparent 4px.
  • Change the tracking-line colour from magenta to green (#00ff00) for a night-vision monochrome monitor look.
  • Slow the scanline drift by increasing the gt-03-scan-drift duration to 15s for a more subtle ambient flicker.
  • Replace the serif headline with a bold condensed sans for a broadcast-news lower-third VHS caption style.
  • Add a second tracking bar with a 2.5s animation-delay offset to simulate dual read-head tracking error.

Gotchas — read before shipping

  • Animating background-position is not compositor-accelerated in all browsers; if jank appears on mobile, switch the scanline element to use a translateY transform instead of background-position scrolling.
  • The multi-stop text-shadow with large blur values can be GPU-intensive on lower-end devices; reduce the outer glow blur from 20px to 8px for a lighter paint budget.
  • The REC indicator uses animation-delay to create the blink offset from the jitter cycle; if you change the jitter duration, verify the REC blink timing still feels independent.

Browser support

ChromeSafariFirefoxEdge
60+12+75+60+

All properties used are baseline; no cutting-edge CSS required.

Techniques used in this demo

Search CodeFronts

Loading…