Glitch Text
The word LOADING at 32 px bold: a red copy of the top half shaking ±2 px every 0.3 s, a static cyan copy of the bottom half — pure CSS, zero JavaScript.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Loaders has 29 effects, including 4 free. Explore the category →
3 usage examples



How it works
The .loader-glitch is a div containing the text LOADING at 32 px, weight 800, color #6366f1 (indigo), position: relative. Two pseudo-elements, ::before and ::after, share position: absolute; left: 0; top: 0 and both carry content: “LOADING” — hard-coded in the CSS, not via attr(data-text). The ::before is red (color: red) and clipped to the top half with clip-path: inset(0 0 50%); the ::after is cyan (color: #0ff) and clipped to the bottom half with clip-path: inset(50% 0 0). The three layers overlap within the same bounding box, measured at 148 × 37 px with the default sans-serif font.
The glitchLoader animation applies only to ::before (the red top half): 0.3s linear infinite alternate-reverse. The keyframes move that layer via translate through five positions: (0, 0) at the ends, (−2 px, +2 px) at 20%, (−2 px, −2 px) at 40%, (+2 px, +2 px) at 60%, (+2 px, −2 px) at 80%. The alternate-reverse direction plays the sequence backward on odd cycles, avoiding a visible reset. Measured: animationName: “glitchLoader” on ::before, animationName: “none” on ::after — the cyan bottom half is static, always aligned with the main text.
Two structural points. First: the displayed word appears in three places — the div innerHTML, ::before { content: “LOADING” } and ::after { content: “LOADING” } — without an attr(data-text) binding; changing the label requires three edits. Second: no @media (prefers-reduced-motion) rule is present; measured under reducedMotion: reduce emulation, the animationPlayState of ::before stays “running”. The code contains no JavaScript — the js field is an empty string.
Accessibility
- prefers-reduced-motion not handled: measured under emulation, the animation keeps running. Add
@media (prefers-reduced-motion: reduce) { .loader-glitch::before { animation: none; } }to freeze the effect. - This element is meant to signal an ongoing operation: the code declares no
role="status",aria-liveoraria-busy. Addrole="status" aria-live="polite" aria-label="Loading"to the.loader-glitchelement. - The
::beforeand::afterpseudo-elements withcontent: "LOADING"are exposed by some screen readers (NVDA, VoiceOver iOS). To avoid triple reading, addspeak: noneon the pseudo-elements via CSS — or replace bothcontentvalues with empty strings and keep the readable text only in thediv. - The three colors (indigo, red, cyan) are decorative and carry no status information. On a dark background (#0a0a0f), all contrast ratios exceed 4.5:1; on a light background, check each hue independently.
Browser compatibility
Requires clip-path: inset(), @keyframes with transform: translate() and CSS pseudo-elements. Zero JavaScript dependency.
Without clip-path (IE 11), the pseudo-elements render without clipping: red and cyan cover the full text, the jitter persists but the top/bottom split disappears.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="loader-glitch">LOADING</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Displayed word (HTML + CSS ::before { content: "…" } and ::after { content: "…" }) |
LOADING | Three occurrences to edit together: the div innerHTML and both content rules. To edit in one place only, replace both content values with attr(data-text) and set the attribute on the element. |
Main color (CSS color: #6366f1) |
#6366f1 (indigo) | Color of the base text, visible at the lateral edges of the letters where pseudo-elements do not cover. |
Top channel color (CSS ::before { color: red }) |
red — rgb(255, 0, 0) | Color of the top half, the only layer that moves. #ff00aa for a magenta/cyan glitch, lime for a terminal look. |
Bottom channel color (CSS ::after { color: #0ff }) |
#0ff (cyan) | Color of the static bottom half. To animate it as well, add animation: glitchLoader 0.3s linear infinite alternate to ::after. |
Animation duration (CSS animation: glitchLoader 0.3s) |
0.3s | 0.1s = rapid jitter (urgency); 0.6s = slow glitch (atmosphere); beyond 1 s, the motion reads more like a vibration than a glitch. |
Shift amplitude (CSS translate(-2px, 2px) in the keyframes) |
±2 px | Edit the eight translate() values across the five keyframe stops. ±1 px = subtle glitch; ±5 px = strong distortion. |
Split point (CSS clip-path: inset(0 0 50%) and inset(50% 0 0)) |
50% — cut at mid-height | inset(0 0 30%) / inset(70% 0 0) gives more animated red and less static cyan. The two values must sum to 100%. |
FAQ
::after has no animation property in the sold CSS — measured: animationName: “none”. Only ::before (red, top half) receives glitchLoader. To animate the bottom half as well, add .loader-glitch::after { animation: glitchLoader 0.3s linear infinite alternate; } — use alternate (not alternate-reverse) so the two channels do not always move in the same direction.div.loader-glitch, then .loader-glitch::before { content: “…” } and .loader-glitch::after { content: “…” } in the CSS. For a single edit point, replace both content values with content: attr(data-text) and set data-text="LOADING" on the element.content: attr(data-text) (text configurable via an attribute, no CSS edit needed), duplicates the text as full-height layers with different clip bands per layer, and is designed as a permanent decorative effect on any text. fx-0438 is a loading indicator with hard-coded text, splits the layers into top/bottom halves and animates only one of them — producing a different, more asymmetric register.