---
name: glitch-text
description: Use when you want "Cyber, edgy, hacker-like" - Creates digital distortion, misalignment, and flickering.
---

# Glitch Text

> **Category:** Text  -  **Personality:** Cyber, edgy, hacker-like
>
> **Best use:** Cybersecurity, gaming, sci-fi
>
> **Ratings:** Professional 2/5 - Casual 3/5 - Exotic 5/5 - Visual impact 5/5 - Difficulty 3/5 - Performance cost 3/5

## What it is

Glitch Text fakes a corrupted digital signal on a single word. It stamps **two extra copies of the same text** behind/over the original — one tinted cyan, one magenta — nudges them a few pixels apart for a chromatic "RGB split", then carves each copy into shifting horizontal bands with `clip-path: inset()`. A keyframe timeline jumps those bands around so the word looks like it is tearing, mis-tracking and flickering like a damaged CRT. It reads as cyber, hacker-ish and dangerous, which is why it belongs on a sci-fi/gaming/security brand hero — used on **one short wordmark**, never body copy.

## Dependencies / CDN

**None - pure CSS** for the effect itself. The demo adds two optional extras:

- A display + monospace font pairing (Chakra Petch for the wordmark, Share Tech Mono for the terminal UI):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
```

- A tiny vanilla-JS character-scramble flourish (no library) that settles the word from noise on load and on hover.

## HTML

The visible text **and** the `data-text` attribute must be identical — `data-text` feeds the two glitch layers:

```html
<div class="gl-stage">
  <!-- the only line that matters for the effect -->
  <h2 class="gl-wordmark" data-text="BLACKWALL" tabindex="0">BLACKWALL</h2>

  <!-- optional supporting UI to sell the scene -->
  <span class="gl-alert">&#9888; Intrusion detected</span>
</div>
```

## CSS

```css
.gl-stage{                          /* dark, slightly busy backdrop so the neon glows */
  --cyan:#00f0ff; --mag:#ff2e6e; --ink:#eaf6ff;
  background:#06080e; padding:60px; border-radius:24px; isolation:isolate;
}

.gl-wordmark{
  font-family:'Chakra Petch',sans-serif; font-weight:700;
  font-size:clamp(44px,12.5vw,140px); letter-spacing:.03em; text-transform:uppercase;
  margin:0; position:relative; color:var(--ink); cursor:crosshair;
  text-shadow:0 0 30px rgba(0,240,255,.22), 0 0 5px rgba(0,240,255,.35);
  animation:gl-flick 6s steps(1,end) infinite;
}
/* two tinted copies of the SAME text, stacked exactly on top */
.gl-wordmark::before,
.gl-wordmark::after{
  content:attr(data-text); position:absolute; left:0; top:0; width:100%;
  text-shadow:none;
}
.gl-wordmark::before{color:var(--cyan); mix-blend-mode:screen; animation:gl-cyan 2.6s steps(1,end) infinite;}
.gl-wordmark::after {color:var(--mag);  mix-blend-mode:screen; animation:gl-mag  3.3s steps(1,end) infinite;}

/* cyan layer — sliced into shifting bands; calm baseline keeps a faint 1px fringe */
@keyframes gl-cyan{
  0%,6%   {clip-path:inset(0 0 0 0);     transform:translate(-1px,0)}
  7%      {clip-path:inset(8% 0 80% 0);  transform:translate(-6px,-1px)}
  9%      {clip-path:inset(55% 0 24% 0); transform:translate(5px,1px)}
  11%     {clip-path:inset(30% 0 54% 0); transform:translate(-5px,0)}
  13%     {clip-path:inset(78% 0 8% 0);  transform:translate(4px,0)}
  15%,57% {clip-path:inset(0 0 0 0);     transform:translate(-1px,0)}   /* long calm */
  59%     {clip-path:inset(44% 0 40% 0); transform:translate(6px,1px)}
  61%     {clip-path:inset(12% 0 74% 0); transform:translate(-6px,0)}
  63%     {clip-path:inset(66% 0 14% 0); transform:translate(4px,-1px)}
  65%,100%{clip-path:inset(0 0 0 0);     transform:translate(-1px,0)}
}
/* magenta layer — DIFFERENT bands + duration so the two never line up */
@keyframes gl-mag{
  0%,19%  {clip-path:inset(0 0 0 0);     transform:translate(1px,0)}
  20%     {clip-path:inset(70% 0 12% 0); transform:translate(6px,1px)}
  22%     {clip-path:inset(22% 0 60% 0); transform:translate(-5px,0)}
  24%     {clip-path:inset(46% 0 32% 0); transform:translate(5px,-1px)}
  26%,75% {clip-path:inset(0 0 0 0);     transform:translate(1px,0)}
  77%     {clip-path:inset(16% 0 72% 0); transform:translate(-6px,0)}
  79%     {clip-path:inset(58% 0 22% 0); transform:translate(5px,1px)}
  81%     {clip-path:inset(34% 0 48% 0); transform:translate(-5px,-1px)}
  83%,100%{clip-path:inset(0 0 0 0);     transform:translate(1px,0)}
}
/* whole-word flicker — fires rarely so it feels like a dropout, not a strobe */
@keyframes gl-flick{
  0%,93%,100%{opacity:1;   transform:skewX(0)}
  94%        {opacity:.78; transform:skewX(2deg)}
  95%        {opacity:1;   transform:skewX(-3deg)}
  96%        {opacity:.9;  transform:skewX(0)}
}

@media (prefers-reduced-motion:reduce){
  .gl-wordmark{animation:none}
  .gl-wordmark::before,.gl-wordmark::after{display:none}   /* crisp, static wordmark */
}
```

## JavaScript

Optional. The effect is fully CSS; this only adds a character-scramble that resolves the word on load and on hover/focus. It updates **both** `textContent` and `data-text` so all three layers scramble in sync. Skipped under reduced-motion.

```js
var el     = document.querySelector('.gl-wordmark');
var reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
var FINAL  = el.getAttribute('data-text');
var GLYPHS = '!<>-_\\/[]{}=+*^?#%01XΛ§Øλ';
var raf, frame = 0, lock = [];

function rnd(){ return GLYPHS.charAt(Math.floor(Math.random()*GLYPHS.length)); }
function set(s){ el.textContent = s; el.setAttribute('data-text', s); }  // keep all 3 layers in sync

function tick(){
  var out = '', done = 0;
  for(var i=0;i<FINAL.length;i++){
    if(frame >= lock[i]){ out += FINAL.charAt(i); done++; }      // this char has locked in
    else out += (FINAL.charAt(i) === ' ' ? ' ' : rnd());        // still scrambling
  }
  set(out);
  if(done === FINAL.length) return;
  frame++; raf = requestAnimationFrame(tick);
}
function scramble(){
  if(reduce) return;
  cancelAnimationFrame(raf); frame = 0; lock = [];
  for(var i=0;i<FINAL.length;i++) lock.push(8 + Math.floor(Math.random()*22));  // stagger lock-in
  tick();
}
el.addEventListener('mouseenter', scramble);
el.addEventListener('focus', scramble);
if(!reduce) setTimeout(scramble, 460);   // play once on arrival
```

## How it works

- **`content: attr(data-text)`** on `::before` and `::after` clones the word into two extra layers without duplicating markup.
- **Two tints + a few px offset** = chromatic aberration (RGB split): cyan pulled one way, magenta the other, so the word looks like a mis-converged screen.
- **`clip-path: inset(top right bottom left)`** carves each clone into a horizontal band; animating the inset values jumps that band around — those are the "torn slices".
- **`mix-blend-mode: screen`** is what keeps it legible: over the bright base letters the cyan/magenta screen back to white (invisible), so the tints only show as coloured fringes where the slices spill onto the dark background.
- **`steps(1, end)`** makes every keyframe *snap* instead of tweening — that hard jump is what reads as "digital" rather than a smooth wipe.
- **Different durations (2.6s vs 3.3s)** keep the two layers permanently out of phase, so they never resolve into a clean word — the unease is continuous, while a calm baseline (a faint 1px fringe) holds between short violent bursts.

## Customization

- **Intensity:** widen the `clip-path` bands and grow the `translate` px for more violence; add/remove burst windows in the `%` timeline. More time spent at `inset(0)` = calmer.
- **Colour:** swap `--cyan` / `--mag`. Classic chromatic split is cyan + red/magenta; for a single-colour "phosphor" glitch tint both layers green and only offset them.
- **Speed / desync:** change the two animation durations; keep them unequal (and not multiples) so the layers drift apart.
- **Analog grain:** layer a `repeating-linear-gradient` of 1px dark lines for scanlines and a radial `vignette` over the panel (the live demo does this) for a full CRT vibe.
- **Type:** any heavy display face works; angular, cut-corner fonts (Chakra Petch, Orbitron, Tektur) sell the cyber tone better than a neutral sans.

## Accessibility & performance

- **Honour `prefers-reduced-motion`:** hide both pseudo layers (`display:none`) and drop the animations so a crisp, static wordmark remains — and bail out of the JS scramble.
- **Legibility first:** the base layer stays solid and bright; `mix-blend-mode: screen` stops the tints from washing over the letters. Glitch one short word, never a sentence.
- **Screen readers:** the real text lives in the element, so AT reads a clean `"BLACKWALL"`; `data-text` is a duplicate and `::before/::after` content is ignored by assistive tech.
- **Cheap to animate:** only `transform`, `clip-path` and `opacity` move — all compositor-friendly. Avoid animating `color`, `filter` or `text-shadow`.
- **Photosensitivity:** keep the flicker subtle and infrequent (a rare dropout, not a strobe); never flash the whole screen.

## Gotchas

- **`data-text` must equal the visible text** — otherwise the coloured layers spell something different from the base.
- **Needs a dark, slightly busy backdrop.** On flat white the neon split has nothing to glow against; `isolation:isolate` on the parent contains the blend so it samples only inside the panel.
- **Paint order is asymmetric:** `::before` paints *below* the text, `::after` *above* it. That is fine here, but if you drop `mix-blend-mode` the `::after` copy will visibly sit on top of the base and crush legibility — keep the blend.
- **`clip-path: inset()` order is top → right → bottom → left.** Mixing it up moves the wrong slice.
- **If you scramble in JS, update BOTH `textContent` and `data-text`** every frame, or the colour layers desync from the base word.
- **`steps(1, end)` is load-bearing.** Linear timing turns the slices into smooth wipes — it looks like a scanner, not a glitch.
- A `transform` on the wordmark (the skew flicker) creates a stacking context; keep the coloured copies as **pseudo-elements of that same element** so they move with it.
