Glitch Text

On hover the word tears into offset slices of colour for a moment, like a signal breaking up, then snaps back clean.

  • CSS
  • Text
  • Hover
  • Added
SIGNAL LOST

AI prompt

Create a glitch text hover effect in pure CSS. Put the word in an element with data-text set to the same string, and add ::before and ::after with content: attr(data-text) stacked exactly on top. Colour one layer with the accent and the other with a cool light tone, and keep them invisible at rest. On :hover and :focus-visible, show both layers and run a 600ms steps(1) keyframe on each that jumps clip-path: inset(top% 0 bottom% 0) between four or five horizontal slices while shifting translate by a few pixels left and right, with the two layers out of phase. Keep the real text readable underneath. Put the word in a visually hidden span for screen readers and mark the glitching element aria-hidden. Under prefers-reduced-motion, show a static two-colour offset instead. The word reads "SIGNAL LOST".

Source

Demo.tsx
'use client'

import s from './demo.module.css'

export default function GlitchText({ labels }: { labels: Record<string, string> }) {
  return (
    <span className={s.wrap} tabIndex={0}>
      <span className={s.sr}>{labels.text}</span>
      <span className={s.glitch} data-text={labels.text} aria-hidden="true">
        {labels.text}
      </span>
    </span>
  )
}
demo.module.css
.wrap {
  border-radius: 6px;
  outline: none;
  cursor: default;
}

.wrap:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 6px; }

.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.glitch {
  position: relative;
  display: inline-block;
  font: 800 clamp(2rem, 7vw, 2.6rem) / 1 var(--font-display);
  letter-spacing: 0.04em;
  color: var(--stage-ink);
}

.glitch::before,
.glitch::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  opacity: 0;
}

.glitch::before { color: var(--stage-accent); }
.glitch::after { color: oklch(80% 0.1 220); }

.wrap:hover .glitch::before,
.wrap:focus-visible .glitch::before { opacity: 1; animation: slice-a 600ms steps(1) infinite; }

.wrap:hover .glitch::after,
.wrap:focus-visible .glitch::after { opacity: 1; animation: slice-b 600ms steps(1) infinite; }

@keyframes slice-a {
  0% { clip-path: inset(10% 0 70% 0); translate: -4px 0; }
  20% { clip-path: inset(55% 0 25% 0); translate: 3px 0; }
  40% { clip-path: inset(30% 0 50% 0); translate: -2px 0; }
  60% { clip-path: inset(80% 0 5% 0); translate: 4px 0; }
  80% { clip-path: inset(0 0 85% 0); translate: -3px 0; }
}

@keyframes slice-b {
  0% { clip-path: inset(65% 0 15% 0); translate: 3px 0; }
  20% { clip-path: inset(5% 0 80% 0); translate: -4px 0; }
  40% { clip-path: inset(40% 0 40% 0); translate: 2px 0; }
  60% { clip-path: inset(20% 0 65% 0); translate: -3px 0; }
  80% { clip-path: inset(75% 0 10% 0); translate: 4px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .wrap:hover .glitch::before,
  .wrap:hover .glitch::after,
  .wrap:focus-visible .glitch::before,
  .wrap:focus-visible .glitch::after { animation: none; clip-path: inset(0 0 50% 0); }
  .wrap:hover .glitch::before, .wrap:focus-visible .glitch::before { translate: -2px 0; }
  .wrap:hover .glitch::after, .wrap:focus-visible .glitch::after { clip-path: inset(50% 0 0 0); translate: 2px 0; }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.