Spot a photo or a video that has been altered
Start · 4 steps, 15 minGenerative Glitch
A white word doubled by two copies — cyan on its top band, magenta on its bottom band — that jitter by 2 to 3 px on 2 s and 3 s cycles: a chromatic glitch in pure CSS, 2 KB, no script and no randomness, read only once by screen readers and frozen at a slight offset when the user reduces motion.
Updated
This effect is free — the Liquid category contains 31 effects total, including 6 free. Effect.Labs has 840 vanilla effects. Explore the category →
Usage examples
Exhibition
40 years of videotapes
Bits of History · episode 12
The millennium bug: how the world fixed its dates in time
How it works
The word is a white, bold (800), 2.5rem <span class="ai-glitch-text">, centered in .ai-glitch-container (background #111). It carries the same text in a data-text attribute, which its two pseudo-elements copy (content: attr(data-text)) in absolute position, exactly on top. ::before is cyan (#0ff) and clipped by clip-path to its top 35%; ::after is magenta (#f0f) and clipped to its bottom 35%. The middle band stays white: the word is always readable.
Each copy has its own jitter animation. aiGlitch1 moves the cyan copy through five fixed positions (−3/+2 px, +3/−1 px, −2/−2 px, +2/+1 px, back to 0) in 2 s; aiGlitch2 moves the magenta copy through three positions (+2/−2 px, −3/+1 px, +1/+2 px) in 3 s. Both run alternate-reverse (forth then back), so over 4 s and 6 s: their movements only line up again every 12 s, which avoids a visible repeat. Nothing is random: the bands and positions are written in the CSS, and the catalog name "Generative" covers no computation and no artificial intelligence. Since only transform is animated, the browser moves the copies without redrawing the text.
The copies' content is written content: attr(data-text) / "": the part after the slash is the alternative text read by screen readers, empty here, so both copies are silent. Measured in Chromium 151, Firefox 153 and WebKit 26: the accessibility tree holds the word only once. The previous line, content: attr(data-text) alone, is the fallback for browsers that do not know this syntax: they show the copies, and may then read the word three times. The text and data-text must stay identical: a copy saying something other than the word would show in the colored bands.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, both copies stop at a slight fixed offset (cyan at −2/+1 px, magenta at +2/−1 px): the picture keeps its colored doubling, with no jitter at all. Measured in all three engines: 0% of pixels changing in 500 ms (against 1% in normal mode, the jitter only touching the letter edges). - Screen readers: the word is read once, the copies having an empty alternative text (
/ ""). The word stays real text, selectable and translatable. - Readability and flashing: the central band stays white and offsets never exceed 3 px: the word reads at every moment. The jitter is continuous but has no flash or brightness alternation, far from the WCAG flash thresholds; on a heading that stays on screen for a long time, consider stopping it after a few seconds (
animation-iteration-count).
Browser compatibility
Pure CSS (clip-path: polygon(), pseudo-elements with attr(), a transform animation), 2 KB, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: same jitter, same offset stop under reduced motion, word read once. Alternative text on pseudo-elements (/ "") exists since Chrome 77, Firefox 128 and Safari 17.4; before that, the fallback line shows the same copies.
Without clip-path (very old browsers), the copies cover the whole word: the doubling stays visible, stronger. Without CSS animations, the copies sit on the word with no offset: you see a three-color word (cyan, white, magenta), still.
The code
Copy the three blocks into your page. No dependencies.
<div class="fx-0416-root">
<div class="ai-glitch-container">
<span class="ai-glitch-text" data-text="GLITCH">GLITCH</span>
</div>
</div>
.fx-0416-root,
.fx-0416-root *,
.fx-0416-root *::before,
.fx-0416-root *::after {
box-sizing: border-box
}
.fx-0416-root {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 250px;
overflow: hidden;
position: relative;
background: rgb(26, 26, 36);
color: rgb(255, 255, 255);
font-family: sans-serif;
}
.ai-glitch-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: #111;
}
.ai-glitch-text {
font-size: 2.5rem;
font-weight: 800;
color: #fff;
position: relative;
}
.ai-glitch-text::before,
.ai-glitch-text::after {
content: attr(data-text);
content: attr(data-text) / "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ai-glitch-text::before {
animation: aiGlitch1 2s infinite linear alternate-reverse;
clip-path: polygon(0 0, 100% 0, 100% 35%, 0 35%);
color: #0ff;
}
.ai-glitch-text::after {
animation: aiGlitch2 3s infinite linear alternate-reverse;
clip-path: polygon(0 65%, 100% 65%, 100% 100%, 0 100%);
color: #f0f;
}
@keyframes aiGlitch1 {
0% {
transform: translate(0);
}
20% {
transform: translate(-3px, 2px);
}
40% {
transform: translate(3px, -1px);
}
60% {
transform: translate(-2px, -2px);
}
80% {
transform: translate(2px, 1px);
}
100% {
transform: translate(0);
}
}
@keyframes aiGlitch2 {
0% {
transform: translate(0);
}
25% {
transform: translate(2px, -2px);
}
50% {
transform: translate(-3px, 1px);
}
75% {
transform: translate(1px, 2px);
}
100% {
transform: translate(0);
}
}
@media (prefers-reduced-motion: reduce) {
.ai-glitch-text::before {
animation: none;
transform: translate(-2px, 1px);
}
.ai-glitch-text::after {
animation: none;
transform: translate(2px, -1px);
}
}
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Text (HTML — content and data-text) |
GLITCH in both |
Write the same text in both places. If the text changes on the page (translation, dynamic content), copy it over in one line: el.dataset.text = el.textContent. |
Copy colors (CSS — color of ::before and ::after) |
cyan #0ff, magenta #f0f |
Red and cyan (#ff2a2a, #00e5ff) give an anaglyph 3D look; two tones close to the text color, a subtle glitch. |
Band height (CSS — clip-path: polygon(…)) |
35% at the top, 35% at the bottom | 20% / 20% leaves more white in the center and a finer effect; 50% / 50% colors the whole word, with no white band. |
Amplitude (CSS — positions in @keyframes aiGlitch1 and aiGlitch2) |
1 to 3 px | Double the values (up to 6 px) for a very large heading; beyond that, the copies come off the word and reading gets tiring. |
Pace (CSS — animation: aiGlitch1 2s …, aiGlitch2 3s …) |
2 s and 3 s, back and forth | Keep two different durations: if equal, the copies move together and the jitter looks mechanical. 0.3 s and 0.5 s give a nervous crackle. |
FAQ
Is the slicing random?
No: both bands are fixed (35% at the top, 35% at the bottom) and the jitter positions are written in the @keyframes. The offset between two cycles of different lengths is what gives a feeling of irregularity. For truly random slicing you need a bit of JavaScript: every 100 to 300 ms, write randomly drawn bounds into two CSS variables and use them in the pseudo-elements' clip-path.
Why repeat the word in data-text?
Pseudo-elements cannot read an element's text, only its attributes: attr(data-text) is the only way, in pure CSS, to give them the same word. That is also why the copies do not follow a changing text: if your site translates or replaces the word, copy it into data-text at the same time.
How does it differ from AI Typo Morph (fx-0419)?
AI Typo Morph changes words: it scrambles the letters into randomly drawn characters, then settles the next word letter by letter, with a bit of JavaScript. Generative Glitch keeps the same word and makes it shake in colors, in pure CSS. Choose Generative Glitch for a fixed heading with a "disturbed signal" look; AI Typo Morph to reveal or cycle through words.