Payments are slowSince 14:32 UTC
- Public APIOPERATIONAL
- DashboardOPERATIONAL
- PaymentsDEGRADED
- Object storageOPERATIONAL
- Outgoing emailOPERATIONAL
- DNSOPERATIONAL
White text with a red copy on the left and a cyan copy on the right that never stop shaking: the colour split of a jumping video signal, in pure CSS.
Updated
This effect is free — the Distortion category contains 31 effects total, including 5 free. Effect.Labs has 840 vanilla effects. Explore the category →
Payments are slowSince 14:32 UTC
Unstable network · retrying in 5 s
The word is written twice in the HTML: inside the .rgbs-text element and in its data-text attribute. The ::before and ::after pseudo-elements copy that attribute with content: attr(data-text), sit exactly on the text (position: absolute, same width) and go behind it with z-index: -1: only their edges show.
The red copy starts 3 px to the left, the cyan one 3 px to the right. Every 0.15 s, the rgbsShiftR animation takes the red copy 5 px left and 2 px down, rgbsShiftC takes the cyan copy 5 px right and 2 px up, then both come back. These distances are computed from --rgbs-shift (3 px): once that value, then 5/3 and 2/3 of it. No JavaScript, no interaction: the shaking starts as soon as the text is shown.
The .fx-0251-root root centres the word in a 280 px tall block on an rgb(10, 10, 15) background. Both values are variables: --rgbs-height: auto and --rgbs-bg: transparent drop the text into your own page, as in the three examples.
content: attr(data-text) / "", an empty alternative text that keeps them out of speech (Chrome 77+, Firefox 128+, Safari 17.4+). Older browsers keep the first declaration and some screen readers may announce the word three times. Measured in all three engines: "GLITCH GLITCH GLITCH" before the repair, "GLITCH" after.--rgbs-shift on a large title (beyond about 6 px). For bigger text or a wider offset, set --rgbs-speed to 0.34s: no cell then changes more than 3 times per second (measured at 12rem).prefers-reduced-motion: reduce, both animations are removed; the copies stay 3 px off each side, still. The red and cyan relief remains, with no flashing at all.--rgbs-color a dark colour (the status page in the examples uses #b45309).cursor: pointer was removed). For a clickable title, put .rgbs-text on a real <a>.Pure CSS (2.5 KB), no dependency. CSS variables, calc() inside keyframes and pseudo-elements. Measured in Chromium 151, Firefox 153 and WebKit 26.5: pixel-identical to the previous version at 0, 40, 75 and 500 ms, two independent copies, no overflow at 390 px, 0 errors.
Without CSS variables, the rules that use them are dropped: the word shows on its own, in the page's text colour, with no copies. Without alternative text support for content, the second declaration is discarded and the copies still show.
Copy the two blocks (HTML and CSS) into your page. No JavaScript, no dependencies.
<div class="fx-0251-root">
<div class="rgbs-text" data-text="GLITCH">GLITCH</div>
</div>
.fx-0251-root,
.fx-0251-root *,
.fx-0251-root *::before,
.fx-0251-root *::after {
box-sizing: border-box
}
/* Settings: change them on .fx-0251-root (or on a parent) */
.fx-0251-root {
--rgbs-size: 3rem; /* text size */
--rgbs-color: #fff; /* main text */
--rgbs-red: #ff0000; /* left copy */
--rgbs-cyan: #00ffff; /* right copy */
--rgbs-shift: 3px; /* copy offset (moves between 1 and 1.67 times this value) */
--rgbs-speed: 0.15s; /* duration of one shake */
--rgbs-bg: rgb(10, 10, 15);
--rgbs-height: 280px; /* auto: the height of the text */
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: var(--rgbs-height);
overflow: hidden;
position: relative;
background: var(--rgbs-bg);
color: var(--rgbs-color);
font-family: sans-serif;
}
.rgbs-text {
font-size: var(--rgbs-size);
font-weight: 900;
position: relative;
z-index: 0;
color: var(--rgbs-color);
}
/* Coloured copies: decorative, skipped by screen readers (the / "" part) */
.rgbs-text::before,
.rgbs-text::after {
content: attr(data-text);
content: attr(data-text) / "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
}
.rgbs-text::before {
color: var(--rgbs-red);
transform: translate(calc(var(--rgbs-shift) * -1), 0);
animation: rgbsShiftR var(--rgbs-speed) infinite;
}
.rgbs-text::after {
color: var(--rgbs-cyan);
transform: translate(var(--rgbs-shift), 0);
animation: rgbsShiftC var(--rgbs-speed) infinite;
}
@keyframes rgbsShiftR {
0%,
100% {
transform: translate(calc(var(--rgbs-shift) * -1), 0);
}
50% {
transform: translate(calc(var(--rgbs-shift) * -5 / 3), calc(var(--rgbs-shift) * 2 / 3));
}
}
@keyframes rgbsShiftC {
0%,
100% {
transform: translate(var(--rgbs-shift), 0);
}
50% {
transform: translate(calc(var(--rgbs-shift) * 5 / 3), calc(var(--rgbs-shift) * -2 / 3));
}
}
/* Reduced motion: the copies stay offset and still (no flashing) */
@media (prefers-reduced-motion: reduce) {
.rgbs-text::before,
.rgbs-text::after {
animation: none;
}
}
Pure CSS — no JavaScript needed.
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Text (HTML — content of .rgbs-text and data-text attribute) |
GLITCH | Write the same text in both places: the content is the word shown in white and read aloud, the attribute feeds the two coloured copies. |
--rgbs-size |
3rem | Text size. Any CSS unit works, including clamp() or cqw to follow the block's width. |
--rgbs-color |
#fff | Main text colour. Use a dark colour on a light background. |
--rgbs-red · --rgbs-cyan |
#ff0000 · #00ffff | Colours of the left and right copies. Magenta and green (#ff00ff · #00ff66) give a more "CRT" split. |
--rgbs-shift |
3px | Starting offset of the copies; they travel up to 5/3 of it. 1 to 1.5 px for small text, 4 to 6 px for a very large title. |
--rgbs-speed |
0.15s | Length of one back-and-forth. 0.34s or more keeps changes under 3 per second, whatever the size. |
--rgbs-bg · --rgbs-height |
rgb(10, 10, 15) · 280px | Root background and height. transparent and auto drop the text into your layout. |
A pseudo-element cannot copy its element's content: content: attr(data-text) only reads an attribute. If the two differ, the copies show the old word behind the new one. When a script changes the text, update the attribute at the same time: el.dataset.text = el.textContent.
Yes. The copies are as wide as the element and inherit its alignment, so they break at the same places as the main word. For long titles over several very large lines, slow --rgbs-speed down (0.34s) to stay below the flashing threshold.
The code as supplied shakes all the time. To keep the motion for hover, add animation-play-state: paused to both pseudo-elements, then running in a .rgbs-text:hover::before, .rgbs-text:hover::after rule; add :focus-visible if the text is a link, for keyboard users.