HTML/CSS/JS Snippets

Mastering the Cyberpunk Glitch Effect: A Pure CSS Tutorial (Line-by-Line Breakdown)

The UI of Cyberpunk 2077, Blade Runner interfaces, and sci-fi terminal screens all share one visual signature: that glitchy, flickering text effect where a second layer of the same word tears sideways in a contrasting colour. It signals “high-tech instability” — and it looks incredible on portfolio sites, gaming landing pages, 404 pages, and dark-mode developer sites.

The good news: this effect is pure CSS. No JavaScript animation libraries, no canvas, no SVG filters. Just clip-path, pseudo-elements, and a @keyframes block.

This tutorial breaks down every line — what it does, why it’s there, and what happens if you remove it. By the end you will understand the technique well enough to apply it to any element: headings, cards, nav links, hero text.

Live Demo

See the full glitch effect running in your browser

View on CodePen →

1. The HTML Structure

The entire effect relies on a single HTML attribute: data-text. To create a glitch, we need two layers of the same text — a real layer and a ghost layer. Instead of writing the text twice in HTML (which confuses screen readers and looks messy), we store the duplicate in a data attribute and let CSS retrieve it.

<a href="#" class="cyber-link" data-text="ahmodmusa.com">
  ahmodmusa.com
</a>

The value of data-text must match the visible text exactly. Later, content: attr(data-text) in the ::after pseudo-element pulls this value and renders the ghost layer automatically — no extra markup needed.

2. Base Styling — The Neon Glow

The foundation is a border + text combination that glows using layered box-shadow and text-shadow. The cyan colour (#00f7ff) is the classic cyberpunk hue — it sits at the blue-green boundary where it reads as “digital” and “electric” simultaneously.

.cyber-link {
  position: relative;       /* Required: anchors the absolute pseudo-elements */
  display: inline-block;
  text-decoration: none;
  font-size: 24px;
  color: #00f7ff;
  border: 2px solid #00f7ff;
  padding: 20px 40px;
  text-transform: uppercase;
  letter-spacing: 4px;      /* Wide tracking reads as cinematic/futuristic */
  font-weight: bold;
  overflow: hidden;          /* Clips pseudo-elements to the button's bounds */
  transition: 0.2s;
  z-index: 1;

  /* Layered neon glow */
  text-shadow: 0 0 5px #00f7ff;
  box-shadow:
    0 0 10px #00f7ff,        /* Outer glow */
    inset 0 0 5px #00f7ff;  /* Inner glow — creates the glass-tube effect */
}

The inset value on box-shadow is what separates a flat neon border from a proper glowing tube. Without it the glow only radiates outward. With it, the button interior also catches light, making it feel like a physical neon sign rather than a CSS border.

3. The Scanline Texture — Retro Monitor Feel

Old CRT monitors projected images line by line, leaving faint horizontal bands across the screen. Simulating this adds depth and signals “this UI belongs to a physical terminal in a dystopian future.” We use ::before and a repeating-linear-gradient to draw the stripes.

.cyber-link::before {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;

  /* Alternating transparent / faint cyan stripes every 4px */
  background: repeating-linear-gradient(
    0deg,
    transparent,
    transparent 2px,
    rgba(0, 247, 255, 0.07) 3px,
    rgba(0, 247, 255, 0.07) 4px
  );

  z-index: -1;           /* Sits behind the text */
  pointer-events: none;  /* Does not interfere with hover/click events */
}

The opacity of 0.07 keeps the scanlines subtle — you want the texture to be felt, not seen. If you increase it above 0.15 the stripes become distracting. Remove the ::before block entirely if you prefer a clean flat look; the glitch animation works independently of this layer.

4. The Glitch Layer — Where the Magic Happens

This is the core technique. The ::after pseudo-element renders a second copy of the text in magenta (#ff00ea) positioned directly on top of the real text. It stays hidden by default. The clip-path property is what makes it look like a glitch rather than just a colour-shifted duplicate — it cuts out only a horizontal strip of the text, making it appear as a torn, displaced slice.

.cyber-link::after {
  content: attr(data-text);  /* Pulls text from the data-text attribute */
  position: absolute;
  top: 0;
  left: -2px;                /* 2px offset left — creates the displacement */
  width: 100%;
  height: 100%;
  padding: 20px 40px;        /* Must match the parent's padding exactly */
  background: #050505;       /* Covers the real text beneath the slice */
  color: #ff00ea;            /* Glitch colour — magenta contrasts cyan */
  border: 2px solid #ff00ea;
  z-index: -2;
  opacity: 0;                /* Hidden until hover */
  transition: opacity 0.1s;

  /* Clips to show only the 20%→50% vertical band of the text */
  clip-path: polygon(0 20%, 100% 20%, 100% 50%, 0 50%);
}

Understanding clip-path here: polygon(0 20%, 100% 20%, 100% 50%, 0 50%) defines a rectangle by its four corners: top-left (0, 20%), top-right (100%, 20%), bottom-right (100%, 50%), bottom-left (0, 50%). This exposes only the middle 30% of the element’s height — like cutting a horizontal strip through the text. The rest is clipped invisible. The animation changes these percentage values rapidly to make the slice jump up and down.

5. Hover & Animation

On hover, three things happen simultaneously: the button inverts to a solid cyan fill (the classic “pressed neon” look), the glitch layer becomes visible, and a keyframe animation rapidly changes the clip-path and transform values to create the jitter.

/* Inverted fill on hover */
.cyber-link:hover {
  background: #00f7ff;
  color: #050505;
  box-shadow: 0 0 30px #00f7ff, 0 0 60px rgba(0, 247, 255, 0.3);
  text-shadow: none;
}

/* Reveal and animate the glitch layer */
.cyber-link:hover::after {
  opacity: 1;
  left: 4px;   /* Shifts to +4px on hover (was -2px at rest — 6px total shift) */
  animation: glitch-jerk 0.3s linear infinite alternate;
}

@keyframes glitch-jerk {
  0% {
    clip-path: polygon(0 20%, 100% 20%, 100% 50%, 0 50%);
    transform: translate(0);
  }
  33% {
    clip-path: polygon(0 5%, 100% 5%, 100% 25%, 0 25%);
    transform: translate(-3px, 1px);
  }
  66% {
    clip-path: polygon(0 60%, 100% 60%, 100% 80%, 0 80%);
    transform: translate(2px, -1px);
  }
  100% {
    clip-path: polygon(0 35%, 100% 35%, 100% 55%, 0 55%);
    transform: translate(-1px);
  }
}

The original tutorial used 3 keyframe steps. The version above uses 4 steps, which makes the jitter feel less rhythmic and more authentically glitchy — real data corruption does not repeat evenly. The alternate direction keyword means the animation plays forward then reverses, doubling the apparent number of positions without adding more keyframes.

Complete Copy-Paste Code

Save this as an .html file and open it in your browser, or drop the CSS into your existing stylesheet.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cyberpunk Glitch Effect</title>
<style>
  body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: #050505;
    font-family: 'Courier New', monospace;
    gap: 40px;
    flex-wrap: wrap;
  }

  /* ── Base ─────────────────────────────── */
  .cyber-link {
    position: relative;
    display: inline-block;
    text-decoration: none;
    font-size: 22px;
    color: var(--neon, #00f7ff);
    border: 2px solid var(--neon, #00f7ff);
    padding: 18px 36px;
    text-transform: uppercase;
    letter-spacing: 4px;
    font-weight: bold;
    overflow: hidden;
    transition: 0.2s;
    z-index: 1;
    text-shadow: 0 0 5px var(--neon, #00f7ff);
    box-shadow: 0 0 10px var(--neon, #00f7ff), inset 0 0 5px var(--neon, #00f7ff);
  }

  /* ── Scanlines ────────────────────────── */
  .cyber-link::before {
    content: '';
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    background: repeating-linear-gradient(
      0deg,
      transparent, transparent 2px,
      rgba(255,255,255,0.04) 3px, rgba(255,255,255,0.04) 4px
    );
    z-index: -1;
    pointer-events: none;
  }

  /* ── Glitch layer ─────────────────────── */
  .cyber-link::after {
    content: attr(data-text);
    position: absolute;
    top: 0; left: -2px;
    width: 100%; height: 100%;
    padding: 18px 36px;
    background: #050505;
    color: var(--glitch, #ff00ea);
    border: 2px solid var(--glitch, #ff00ea);
    z-index: -2;
    opacity: 0;
    transition: opacity 0.1s;
    clip-path: polygon(0 20%, 100% 20%, 100% 50%, 0 50%);
  }

  /* ── Hover ────────────────────────────── */
  .cyber-link:hover {
    background: var(--neon, #00f7ff);
    color: #050505;
    box-shadow: 0 0 30px var(--neon, #00f7ff), 0 0 60px rgba(0,247,255,.3);
    text-shadow: none;
  }

  .cyber-link:hover::after {
    opacity: 1;
    left: 4px;
    animation: glitch-jerk 0.3s linear infinite alternate;
  }

  /* ── Keyframes ────────────────────────── */
  @keyframes glitch-jerk {
    0%   { clip-path: polygon(0 20%, 100% 20%, 100% 50%, 0 50%); transform: translate(0); }
    33%  { clip-path: polygon(0  5%, 100%  5%, 100% 25%, 0 25%); transform: translate(-3px,  1px); }
    66%  { clip-path: polygon(0 60%, 100% 60%, 100% 80%, 0 80%); transform: translate( 2px, -1px); }
    100% { clip-path: polygon(0 35%, 100% 35%, 100% 55%, 0 55%); transform: translate(-1px); }
  }

  /* ── Reduced motion — accessibility ───── */
  @media (prefers-reduced-motion: reduce) {
    .cyber-link:hover::after {
      animation: none;
      opacity: 0.7;
      left: 3px;
    }
  }

  /* ── Colour variants ──────────────────── */
  .cyan   { --neon: #00f7ff; --glitch: #ff00ea; }
  .green  { --neon: #00ff87; --glitch: #ff3c3c; }
  .amber  { --neon: #ffb300; --glitch: #7c3aed; }
</style>
</head>
<body>

<a href="#" class="cyber-link cyan"  data-text="Cyan / Magenta">Cyan / Magenta</a>
<a href="#" class="cyber-link green" data-text="Green / Red">Green / Red</a>
<a href="#" class="cyber-link amber" data-text="Amber / Violet">Amber / Violet</a>

</body>
</html>

Colour Variants

The complete code above uses CSS custom properties (--neon and --glitch) so switching colour schemes requires only two values per variant. Three pre-built combinations that work well together:

Cyan / Magenta

–neon: #00f7ff
–glitch: #ff00ea

Classic cyberpunk. Ideal for dark portfolios and gaming sites.

Green / Red

–neon: #00ff87
–glitch: #ff3c3c

Matrix terminal energy. Great for hacker-aesthetic or dev tool UIs.

Amber / Violet

–neon: #ffb300
–glitch: #7c3aed

Retro-futurism. Works well with warm dark backgrounds like #0d0800.

Customisation Guide

Change the animation speed: The 0.3s in animation: glitch-jerk 0.3s controls how fast the slice cycles. Values between 0.15s (frenetic, unstable) and 0.5s (slow, eerie) produce very different moods. Above 0.6s the movement becomes too slow to read as a glitch.

Change the slice thickness: The two percentage values in each clip-path keyframe control the height of the visible band. polygon(0 20%, 100% 20%, 100% 50%, 0 50%) shows a 30% band. Make it polygon(0 20%, 100% 20%, 100% 30%, 0 30%) for a thin razor slice, or polygon(0 0%, 100% 0%, 100% 100%, 0 100%) to reveal the full ghost text (no clipping).

Apply to a heading instead of a link: Replace the a tag selector with h1, h2, or any block element. Add display: inline-block if the element needs to wrap tightly around its text. The data-text attribute and content: attr(data-text) technique works on any HTML element.

Make it always glitch (not just on hover): Remove the :hover condition from .cyber-link:hover::after and set opacity: 1 directly on .cyber-link::after. Add the animation there too. This creates a permanently glitching element — use sparingly as it draws strong attention.

Accessibility: Respecting Reduced Motion

Some users have vestibular disorders where flashing or rapidly moving animations trigger dizziness or nausea. The prefers-reduced-motion media query detects when a user has enabled “Reduce Motion” in their operating system and lets you provide a calmer alternative.

The complete code above already includes this:

@media (prefers-reduced-motion: reduce) {
  .cyber-link:hover::after {
    animation: none;   /* No jitter */
    opacity: 0.7;      /* Ghost layer still visible, just static */
    left: 3px;         /* Keeps the colour-shift displacement */
  }
}

This preserves the visual style of the effect (the magenta ghost text is still visible on hover) while removing the rapid movement entirely. The user still sees “cyberpunk” — they just do not experience the animation.

Where to Use This Effect

This technique works best on short, high-contrast text against a very dark background. Ideal use cases: hero CTAs on dark landing pages, navigation links on gaming or portfolio sites, the main headline on a 404 error page, chapter titles in long-form creative writing, and interactive terminal-style UI components.

It does not suit body text, long paragraphs, or light-background designs. The effect relies on the dark background being visible through the scanlines and glitch layer — on a white background, the pseudo-element backgrounds would need to be white too, which changes the entire composition.

More Cyberpunk CSS Effects

This glitch button is part of a series of cyberpunk UI experiments on this site. Each one teaches a different CSS technique:

Need a custom UI built?

I Build Cyberpunk & Futuristic Web Interfaces

Dark landing pages, gaming site UIs, interactive portfolio designs, or any bespoke CSS/JS effect — I design and code from scratch, no templates. Starting at $10 on Fiverr.

Hire Me on Fiverr — Starting at $10

Ahmod Musa

Ahmad Musa is a WordPress architect and developer specializing in performance optimization and modern WordPress architectures. He helps small and medium-sized businesses make smart technology decisions — including when hybrid headless is the right move and when it isn't. Visit ahmodmusa.com for more in-depth WordPress guides, or work with Ahmad directly on Fiverr for professional hybrid headless setup, WordPress speed audits, and architecture consulting.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button