Buttons✨ Premium

Glitch Effect

Two copies of the label, sliced into bands by clip-path, jump ±3 px with magenta and cyan ghosts — a permanent glitch in pure CSS.

CSSAnimationGlitch

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. Buttons has 27 effects, including 2 free. Explore the category →

3 usage examples

“Play” button — esports tournament or game landing page — Glitch Effect example 1

① “Play” button — esports tournament or game landing page

WhenDark hero of a game or tournament site: all-caps title, date, one entry button.
WhyThe visual vocabulary (chromatic aberration, scanlines) belongs to the medium; the permanent glitch plays the part of a blinking “insert coin”.
SettingstranslateX raised to ±5 px, 3 px shadows, 1.1 s / 1.3 s cycles for a twitchier feel.
404 page — back to home — Glitch Effect example 2

② 404 page — back to home

WhenError page: a big “404”, one sentence, one return button.
WhyThe glitch tells the error's story (corrupted signal) and fills a page that is empty by nature. A single animated element: the message stays readable.
SettingsCycles slowed to 2.2 s / 2.6 s (lazy glitch), #0f0f14 background, magenta and cyan shadows kept.
Hackathon registration — tech event card — Glitch Effect example 3

③ Hackathon registration — tech event card

WhenCard listing an event (name, date, seats left) with a registration button.
WhyOn a card, the effect works as a “tech” brand accent with no library or image; the short label stays crisp.
Settings#0ea5e9 background, shadows #f0f → #fff and #0ff → #000 for a monochrome glitch, translateX ±2 px.

How it works

.btn-glitch::before and ::after re-create the label with content: attr(data-text) — the button's data-text attribute must carry the same text as its content. Both pseudo-elements are position: absolute; top: 0; width: 100%; height: 100%, centered with display: flex, and share the button's #6366f1 background: wherever a band is visible, it fully covers the original text.

Each copy shows a single horizontal band. ::before is offset left: 2px with a magenta ghost (text-shadow: -2px 0 #f0f) and animated by glitchTop (1.4 s); ::after is offset left: -2px with a cyan ghost (text-shadow: 2px 0 #0ff) and animated by glitchBottom (1.7 s). Each keyframe's clip-path: inset(top 0 bottom) cuts a band between 22% and 66% of the height — where the text sits.

steps(1, end) is what gives the effect its character: no interpolation between keyframes. Every 20% of the cycle (280 ms for the top band, 340 ms for the bottom one), the band jumps to a new position and a new translateX between −3 and +3 px. The two durations share no divisor: the pattern only repeats every 23.8 s and looks random.

overflow: hidden on .demo-btn clips the ±3 px overflow at the edges, and position: relative on .btn-glitch anchors the pseudo-elements. The animation runs from load, with no hover: the button is never still.

Accessibility

  • prefers-reduced-motion not handled: a permanent movement in the field of view. Add @media (prefers-reduced-motion: reduce) { .btn-glitch::before, .btn-glitch::after { display: none } } — what remains is an ordinary indigo button with a perfectly readable label.
  • Screen readers: content: attr(data-text) is generated content, which Chrome, Firefox and Safari include in a button's accessible name — the label may be announced three times (“Play Play Play”). Set aria-label on the button with the visible text: it becomes the accessible name and the pseudo-elements are ignored.
  • Readability: at any instant one band shifts part of the glyphs by up to 3 px and adds colored ghosts. Fine for a short label (one or two words, 16 px, weight 600); avoid it on long text or below 14 px.
  • Contrast: white text on #6366f1 = 4.47:1, just under the 4.5:1 AA threshold; the magenta and cyan ghosts further degrade local legibility. #4f46e5 gives 6.3:1 — change it in all three places (button and both pseudo-elements).
  • Flicker: the bands change 3.6 and 2.9 times per second, but the moving area is a few pixels tall on a small element — far from the full-screen flashes WCAG 2.3.1 targets. The reduced-motion rule above remains the right safeguard for motion-sensitive users. Native focus ring preserved (no outline: none).

Browser compatibility

Requires unprefixed clip-path: inset(), content: attr(), the steps() timing function and text-shadow. No JavaScript, zero dependencies.

Chrome 55+✓ Full
Firefox 54+✓ Full
Safari 13.1+✓ Full (9.1–13 with -webkit-)
Edge 79+✓ Full
Mobile iOS 13.4+✓ Full
Android Chrome✓ Full

Without clip-path, both copies render whole on top of each other: the text stays readable but moves as a block by ±3 px with its colored shadows. Duplicate the declarations as -webkit-clip-path for Safari 9.1 to 13.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
data-text (HTML — button attribute) same as the label Text of both copies. Must stay equal to the visible label; in a framework, bind both to the same variable.
animation-duration (CSS — ::before 1.4s / ::after 1.7s) 1.4s / 1.7s Jump speed (five per cycle). Keep two durations with no common divisor to avoid a visible pattern; 0.8s / 1.1s = frantic.
translateX (CSS — @keyframes glitchTop / glitchBottom) −3 to +3 px Horizontal shake amplitude. ±6 px visibly tears the text; ±1 px gives a shiver.
clip-path: inset(…) (CSS — each keyframe) bands between 22% and 66% of the height Position and thickness of the slices. They target the text zone (≈ 30–70% of the height with the default padding) — readjust if you change the padding.
text-shadow (CSS — ::before #f0f / ::after #0ff) −2px 0 #f0f / 2px 0 #0ff Chromatic aberration. A single channel (#f0f only) is more restrained; 0 0 6px instead of the offset gives a halo rather than a double image.
left: 2px / -2px (CSS — ::before / ::after) ±2px Base offset of the copies from the original text. At 0, the copies differ only by their shadows.
steps(1, end) (CSS — animation-timing-function) steps(1, end) Jump with no interpolation. steps(2, end) adds an intermediate position; linear turns the glitch into a continuous slide — no longer a glitch.

FAQ

Yes. At rest, hide the copies: .btn-glitch::before, .btn-glitch::after { display: none }. Show them again with their animations under .btn-glitch:hover::before and .btn-glitch:hover::after (display: flex plus each one's animation declaration). The animation restarts from its 0% on every hover.
The bands display data-text, not the button's content. If you change the label (translation, a “Loading…” state), update the attribute at the same time: btn.dataset.text = btn.textContent. A missing attribute yields empty bands: the text then looks punched through.
position: relative turns the position: absolute ::before/::after into layers exactly superimposed on the button (top: 0; width/height: 100%). overflow: hidden clips what sticks out when the copies slide by ±3 px and because of the left: ±2px — without it, colored edges appear outside the button.