30 CSS Hover Effects02 / 30

Pure CSSMIT licensed

CSS Text Glitch Hover Effect

Four text glitch hover effects — RGB channel split, scanline flicker, noise slice, and VHS chromatic aberration — each triggered by hovering the element and driven entirely by CSS keyframes on pseudo-elements using clip-path and color-mix.

Published

Live Demo
Try it

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

The code

<div class="hv-02">
  <div class="hv-02__grid">
    <div class="hv-02__cell">
      <span class="hv-02__glitch hv-02__glitch--rgb" data-text="GLITCH">GLITCH</span>
      <span class="hv-02__label">RGB Channel Split</span>
    </div>
    <div class="hv-02__cell">
      <span class="hv-02__glitch hv-02__glitch--scan" data-text="SCANLINE">SCANLINE</span>
      <span class="hv-02__label">Scanline Flicker</span>
    </div>
    <div class="hv-02__cell">
      <span class="hv-02__glitch hv-02__glitch--slice" data-text="NOISE">NOISE</span>
      <span class="hv-02__label">Noise Slice</span>
    </div>
    <div class="hv-02__cell">
      <span class="hv-02__glitch hv-02__glitch--vhs" data-text="VHS">VHS</span>
      <span class="hv-02__label">VHS Chromatic</span>
    </div>
  </div>
</div>
.hv-02,
.hv-02 *,
.hv-02 *::before,
.hv-02 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.hv-02 ::selection {
  background: #ff003c;
  color: #fff;
}

.hv-02 {
  --bg: #080810;
  --text: #f0f0f0;
  --dim: #444;
  --red: #ff003c;
  --blue: #00e5ff;
  --green: #39ff14;
  --yellow: #ffe600;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 60px 24px;
}

.hv-02__grid {
  display: grid;
  grid-template-columns: repeat(2,1fr);
  gap: 48px;
  max-width: 800px;
  width: 100%;
}

.hv-02__cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 60px 32px;
  background: rgba(255,255,255,.025);
  border: 1px solid rgba(255,255,255,.07);
  border-radius: 12px;
}

.hv-02__label {
  font-size: 11px;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--dim);
}
/* shared glitch base */

.hv-02__glitch {
  font-size: clamp(2rem,6vw,3.5rem);
  font-weight: 900;
  color: var(--text);
  letter-spacing: .08em;
  position: relative;
  cursor: default;
  display: inline-block;
}

.hv-02__glitch::before,
.hv-02__glitch::after {
  content: attr(data-text);
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
/* 1 — RGB channel split */

.hv-02__glitch--rgb::before {
  color: var(--red);
  left: 2px;
}

.hv-02__glitch--rgb::after {
  color: var(--blue);
  left: -2px;
}

.hv-02__glitch--rgb:hover::before {
  animation: hv-02-rgb-b .4s steps(2) infinite;
}

.hv-02__glitch--rgb:hover::after {
  animation: hv-02-rgb-a .4s steps(2) infinite .1s;
}

@keyframes hv-02-rgb-b {
  0% {
    clip-path: inset(0 0 95% 0);
    transform: translateX(-3px);
  }

  20% {
    clip-path: inset(30% 0 50% 0);
    transform: translateX(3px);
  }

  40% {
    clip-path: inset(60% 0 20% 0);
    transform: translateX(-2px);
  }

  60% {
    clip-path: inset(10% 0 75% 0);
    transform: translateX(4px);
  }

  80% {
    clip-path: inset(80% 0 5% 0);
    transform: translateX(-3px);
  }

  100% {
    clip-path: inset(45% 0 40% 0);
    transform: translateX(2px);
  }
}

@keyframes hv-02-rgb-a {
  0% {
    clip-path: inset(50% 0 30% 0);
    transform: translateX(3px);
  }

  25% {
    clip-path: inset(5% 0 80% 0);
    transform: translateX(-4px);
  }

  50% {
    clip-path: inset(70% 0 15% 0);
    transform: translateX(2px);
  }

  75% {
    clip-path: inset(20% 0 60% 0);
    transform: translateX(-3px);
  }

  100% {
    clip-path: inset(40% 0 45% 0);
    transform: translateX(3px);
  }
}
/* 2 — scanline flicker */

.hv-02__glitch--scan {
  color: var(--green);
}

.hv-02__glitch--scan::before {
  background: repeating-linear-gradient(
      0deg,transparent,transparent 2px,rgba(0,0,0,.4) 2px,rgba(0,0,0,.4) 4px
    );
  background-size: 100% 4px;
  mix-blend-mode: multiply;
  opacity: 0;
  transition: opacity .2s;
}

.hv-02__glitch--scan::after {
  color: var(--green);
  left: 2px;
  opacity: 0;
  transition: opacity .2s;
}

.hv-02__glitch--scan:hover::before {
  opacity: 1;
  animation: hv-02-scan-roll .8s linear infinite;
}

.hv-02__glitch--scan:hover::after {
  opacity: .7;
  animation: hv-02-rgb-b .3s steps(3) infinite;
}

@keyframes hv-02-scan-roll {
  0% {
    background-position-y: 0;
  }

  100% {
    background-position-y: 100px;
  }
}
/* 3 — noise slice */

.hv-02__glitch--slice {
  color: var(--yellow);
}

.hv-02__glitch--slice::before {
  color: var(--red);
  clip-path: inset(0 0 100% 0);
}

.hv-02__glitch--slice::after {
  color: var(--blue);
  clip-path: inset(100% 0 0 0);
}

.hv-02__glitch--slice:hover::before {
  animation: hv-02-slice-t .5s steps(4) infinite;
}

.hv-02__glitch--slice:hover::after {
  animation: hv-02-slice-b .5s steps(4) infinite .15s;
}

@keyframes hv-02-slice-t {
  0% {
    clip-path: inset(0 0 85% 0);
    transform: translateX(6px) skewX(-2deg);
  }

  33% {
    clip-path: inset(25% 0 55% 0);
    transform: translateX(-5px);
  }

  66% {
    clip-path: inset(60% 0 10% 0);
    transform: translateX(4px) skewX(1deg);
  }

  100% {
    clip-path: inset(10% 0 75% 0);
    transform: translateX(-6px);
  }
}

@keyframes hv-02-slice-b {
  0% {
    clip-path: inset(80% 0 0 0);
    transform: translateX(-4px);
  }

  33% {
    clip-path: inset(50% 0 20% 0);
    transform: translateX(6px);
  }

  66% {
    clip-path: inset(15% 0 70% 0);
    transform: translateX(-3px) skewX(2deg);
  }

  100% {
    clip-path: inset(65% 0 5% 0);
    transform: translateX(5px);
  }
}
/* 4 — VHS chromatic */

.hv-02__glitch--vhs {
  color: #fff;
}

.hv-02__glitch--vhs::before {
  color: var(--red);
  mix-blend-mode: screen;
  transform: translateX(0);
}

.hv-02__glitch--vhs::after {
  color: var(--blue);
  mix-blend-mode: screen;
  transform: translateX(0);
}

.hv-02__glitch--vhs:hover::before {
  animation: hv-02-vhs-r .6s steps(3) infinite;
}

.hv-02__glitch--vhs:hover::after {
  animation: hv-02-vhs-b .6s steps(3) infinite .2s;
}

@keyframes hv-02-vhs-r {
  0% {
    transform: translateX(-6px);
    clip-path: inset(20% 0 60% 0);
  }

  50% {
    transform: translateX(4px);
    clip-path: inset(55% 0 10% 0);
  }

  100% {
    transform: translateX(-5px);
    clip-path: inset(5% 0 80% 0);
  }
}

@keyframes hv-02-vhs-b {
  0% {
    transform: translateX(6px);
    clip-path: inset(40% 0 30% 0);
  }

  50% {
    transform: translateX(-4px);
    clip-path: inset(10% 0 65% 0);
  }

  100% {
    transform: translateX(5px);
    clip-path: inset(70% 0 5% 0);
  }
}

@media (max-width: 520px) {
  .hv-02__grid {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hv-02__glitch::before,
    .hv-02__glitch::after {
    animation: none!important;
    opacity: 0!important;
  }
}
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 Hover 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: CSS Text Glitch Hover Effect
Source: https://codefronts.com/motion/css-hover-effects/css-text-glitch-hover-effect/

Four text glitch hover effects — RGB channel split, scanline flicker, noise slice, and VHS chromatic aberration — each triggered by hovering the element and driven entirely by CSS keyframes on pseudo-elements using clip-path and color-mix.
## HTML
```html
<div class="hv-02">
  <div class="hv-02__grid">
    <div class="hv-02__cell">
      <span class="hv-02__glitch hv-02__glitch--rgb" data-text="GLITCH">GLITCH</span>
      <span class="hv-02__label">RGB Channel Split</span>
    </div>
    <div class="hv-02__cell">
      <span class="hv-02__glitch hv-02__glitch--scan" data-text="SCANLINE">SCANLINE</span>
      <span class="hv-02__label">Scanline Flicker</span>
    </div>
    <div class="hv-02__cell">
      <span class="hv-02__glitch hv-02__glitch--slice" data-text="NOISE">NOISE</span>
      <span class="hv-02__label">Noise Slice</span>
    </div>
    <div class="hv-02__cell">
      <span class="hv-02__glitch hv-02__glitch--vhs" data-text="VHS">VHS</span>
      <span class="hv-02__label">VHS Chromatic</span>
    </div>
  </div>
</div>
```
## CSS
```css
.hv-02,
.hv-02 *,
.hv-02 *::before,
.hv-02 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.hv-02 ::selection {
  background: #ff003c;
  color: #fff;
}

.hv-02 {
  --bg: #080810;
  --text: #f0f0f0;
  --dim: #444;
  --red: #ff003c;
  --blue: #00e5ff;
  --green: #39ff14;
  --yellow: #ffe600;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: var(--bg);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 60px 24px;
}

.hv-02__grid {
  display: grid;
  grid-template-columns: repeat(2,1fr);
  gap: 48px;
  max-width: 800px;
  width: 100%;
}

.hv-02__cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 60px 32px;
  background: rgba(255,255,255,.025);
  border: 1px solid rgba(255,255,255,.07);
  border-radius: 12px;
}

.hv-02__label {
  font-size: 11px;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--dim);
}
/* shared glitch base */

.hv-02__glitch {
  font-size: clamp(2rem,6vw,3.5rem);
  font-weight: 900;
  color: var(--text);
  letter-spacing: .08em;
  position: relative;
  cursor: default;
  display: inline-block;
}

.hv-02__glitch::before,
.hv-02__glitch::after {
  content: attr(data-text);
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
/* 1 — RGB channel split */

.hv-02__glitch--rgb::before {
  color: var(--red);
  left: 2px;
}

.hv-02__glitch--rgb::after {
  color: var(--blue);
  left: -2px;
}

.hv-02__glitch--rgb:hover::before {
  animation: hv-02-rgb-b .4s steps(2) infinite;
}

.hv-02__glitch--rgb:hover::after {
  animation: hv-02-rgb-a .4s steps(2) infinite .1s;
}

@keyframes hv-02-rgb-b {
  0% {
    clip-path: inset(0 0 95% 0);
    transform: translateX(-3px);
  }

  20% {
    clip-path: inset(30% 0 50% 0);
    transform: translateX(3px);
  }

  40% {
    clip-path: inset(60% 0 20% 0);
    transform: translateX(-2px);
  }

  60% {
    clip-path: inset(10% 0 75% 0);
    transform: translateX(4px);
  }

  80% {
    clip-path: inset(80% 0 5% 0);
    transform: translateX(-3px);
  }

  100% {
    clip-path: inset(45% 0 40% 0);
    transform: translateX(2px);
  }
}

@keyframes hv-02-rgb-a {
  0% {
    clip-path: inset(50% 0 30% 0);
    transform: translateX(3px);
  }

  25% {
    clip-path: inset(5% 0 80% 0);
    transform: translateX(-4px);
  }

  50% {
    clip-path: inset(70% 0 15% 0);
    transform: translateX(2px);
  }

  75% {
    clip-path: inset(20% 0 60% 0);
    transform: translateX(-3px);
  }

  100% {
    clip-path: inset(40% 0 45% 0);
    transform: translateX(3px);
  }
}
/* 2 — scanline flicker */

.hv-02__glitch--scan {
  color: var(--green);
}

.hv-02__glitch--scan::before {
  background: repeating-linear-gradient(
      0deg,transparent,transparent 2px,rgba(0,0,0,.4) 2px,rgba(0,0,0,.4) 4px
    );
  background-size: 100% 4px;
  mix-blend-mode: multiply;
  opacity: 0;
  transition: opacity .2s;
}

.hv-02__glitch--scan::after {
  color: var(--green);
  left: 2px;
  opacity: 0;
  transition: opacity .2s;
}

.hv-02__glitch--scan:hover::before {
  opacity: 1;
  animation: hv-02-scan-roll .8s linear infinite;
}

.hv-02__glitch--scan:hover::after {
  opacity: .7;
  animation: hv-02-rgb-b .3s steps(3) infinite;
}

@keyframes hv-02-scan-roll {
  0% {
    background-position-y: 0;
  }

  100% {
    background-position-y: 100px;
  }
}
/* 3 — noise slice */

.hv-02__glitch--slice {
  color: var(--yellow);
}

.hv-02__glitch--slice::before {
  color: var(--red);
  clip-path: inset(0 0 100% 0);
}

.hv-02__glitch--slice::after {
  color: var(--blue);
  clip-path: inset(100% 0 0 0);
}

.hv-02__glitch--slice:hover::before {
  animation: hv-02-slice-t .5s steps(4) infinite;
}

.hv-02__glitch--slice:hover::after {
  animation: hv-02-slice-b .5s steps(4) infinite .15s;
}

@keyframes hv-02-slice-t {
  0% {
    clip-path: inset(0 0 85% 0);
    transform: translateX(6px) skewX(-2deg);
  }

  33% {
    clip-path: inset(25% 0 55% 0);
    transform: translateX(-5px);
  }

  66% {
    clip-path: inset(60% 0 10% 0);
    transform: translateX(4px) skewX(1deg);
  }

  100% {
    clip-path: inset(10% 0 75% 0);
    transform: translateX(-6px);
  }
}

@keyframes hv-02-slice-b {
  0% {
    clip-path: inset(80% 0 0 0);
    transform: translateX(-4px);
  }

  33% {
    clip-path: inset(50% 0 20% 0);
    transform: translateX(6px);
  }

  66% {
    clip-path: inset(15% 0 70% 0);
    transform: translateX(-3px) skewX(2deg);
  }

  100% {
    clip-path: inset(65% 0 5% 0);
    transform: translateX(5px);
  }
}
/* 4 — VHS chromatic */

.hv-02__glitch--vhs {
  color: #fff;
}

.hv-02__glitch--vhs::before {
  color: var(--red);
  mix-blend-mode: screen;
  transform: translateX(0);
}

.hv-02__glitch--vhs::after {
  color: var(--blue);
  mix-blend-mode: screen;
  transform: translateX(0);
}

.hv-02__glitch--vhs:hover::before {
  animation: hv-02-vhs-r .6s steps(3) infinite;
}

.hv-02__glitch--vhs:hover::after {
  animation: hv-02-vhs-b .6s steps(3) infinite .2s;
}

@keyframes hv-02-vhs-r {
  0% {
    transform: translateX(-6px);
    clip-path: inset(20% 0 60% 0);
  }

  50% {
    transform: translateX(4px);
    clip-path: inset(55% 0 10% 0);
  }

  100% {
    transform: translateX(-5px);
    clip-path: inset(5% 0 80% 0);
  }
}

@keyframes hv-02-vhs-b {
  0% {
    transform: translateX(6px);
    clip-path: inset(40% 0 30% 0);
  }

  50% {
    transform: translateX(-4px);
    clip-path: inset(10% 0 65% 0);
  }

  100% {
    transform: translateX(5px);
    clip-path: inset(70% 0 5% 0);
  }
}

@media (max-width: 520px) {
  .hv-02__grid {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hv-02__glitch::before,
    .hv-02__glitch::after {
    animation: none!important;
    opacity: 0!important;
  }
}
```

How this works

All four variants use the same core trick: ::before and ::after pseudo-elements carry the same text via the data-text attribute read through attr() in content, then are tinted different hues (color: var(--red) / color: var(--blue)) and offset a few pixels on the X axis. When the parent is hovered, a keyframe fires that randomly repositions clip-path: inset(N% 0 M% 0) values creating the slice effect.

The VHS variant adds mix-blend-mode: screen to the pseudo-elements so the RGB channels blend additively against the dark background, producing authentic chromatic fringing. The scanline effect overlays a repeating-linear-gradient on the ::before with a tiny period (3px stripes) and animates its background-position-y to simulate a rolling scan head.

Make it yours

  • Increase glitch intensity by widening the pixel offset in the keyframe: change translateX(-4px) to translateX(-10px) on both pseudo-elements.
  • Control flicker speed by editing animation-duration — try 0.2s for chaotic jitter or 0.8s for a slower VHS-style drift.
  • Swap from triggered-on-hover to always-on by removing the :hover prefix from the animation declarations.
  • Change channel colours by updating --red and --blue to any two hues — e.g. #ff0 + #0f0 for a phosphor-green terminal.
  • Add a letter-spacing transition alongside the glitch for a typeface-jitter feel: letter-spacing: 0.05em on hover.

Gotchas — read before shipping

  • content: attr(data-text) only works when the attribute is present in HTML — if the attribute is missing the pseudo-element renders empty and the glitch is invisible.
  • mix-blend-mode: screen on the VHS variant requires a dark background to read correctly; on light backgrounds the blended channels wash out to white.
  • Rapid clip-path keyframes can cause layout thrashing on older mobile GPUs — add will-change: clip-path to the pseudo-elements only when the effect is consistently janky.

Browser support

ChromeSafariFirefoxEdge
75+13.1+72+75+

clip-path inset and mix-blend-mode are both baseline modern — no polyfills required.

Techniques used in this demo

Search CodeFronts

Loading…