
CSS Shake Animation on Invalid Input
Published
30 hand-coded CSS shake animation topics, each shipping 2–3 side-by-side variants so you can pick the exact treatment for your surface — form-error recoils, login box shakes, button attention wiggles, notification bell rings, cart wiggles, icon micro-interactions, elastic springs, 3D judders, and hit-damage / screen shakes for browser games. Every form demo pairs motion with color + border + aria-invalid so the state reads without motion perception. Scoped under .shk-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Related16 CSS Glitch Text Effects16 CSS Bounce Animations30 CSS Keyframe Animations

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published
Published

Published

Published
Published
Published

Published

Published

Published

Published

Published

Published

Published

Published

Published
transform — used almost exclusively as a micro-interaction to signal state change: form validation failure, wrong password, button attention pull, notification arrival, cart item added, incoming hit / damage in a browser game, or a full-viewport screen shake for a big narrative moment. It's distinct from bounce (a vertical elastic drop / settle), glitch (a RGB channel-split chromatic aberration), wobble (a slower rotational sway), and pulse (a scale-based attention loop). The canonical recipe in 20 lines: @keyframes shake { 10%, 90% { transform: translateX(-1px); } 20%, 80% { transform: translateX(2px); } 30%, 50%, 70% { transform: translateX(-4px); } 40%, 60% { transform: translateX(4px); } } paired with .shake { animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both; transform: translate3d(0, 0, 0); backface-visibility: hidden; perspective: 1000px; }. The critical details most tutorials get wrong: (1) animate transform only — NEVER margin / left / right, those trigger layout thrash and scrollbars dance during the shake; (2) use asymmetric offsets (10%/20%/30% etc.) rather than a plain sine wave — real shakes decay, they don't cycle; (3) force GPU compositor promotion with translate3d(0,0,0) + backface-visibility: hidden so the animation runs on the compositor thread (zero INP hit); (4) animation-fill-mode: both so the element returns to its rest position cleanly after the shake; (5) wrap in @media (prefers-reduced-motion: reduce) { animation: none; } — non-optional per WCAG 2.3.3. This collection ships 30 topics × 2–3 variants each covering every canonical use case: invalid-input error shakes, form-submit failure recoils, password wrong-attempt shakes, hover attention wiggles, notification bell rings, cart wiggles, icon micro-interactions, and gaming screen shakes.translateX shake paired with a red border + inline error message (never just the shake — WCAG 1.4.1 Use of Color forbids relying on motion alone). Login box wrong-password recoil: topic 08 — same technique scoped to the whole card, not just the input, so the visual weight matches the emotional weight of a failed auth. Button hover attention pull (CTAs, sale banners, limited-time offers): topics 09–13 — subtle 2–3px shakes triggered on :hover, always paired with a color/scale change so touch users still get feedback. Buy-now / add-to-cart CTA: topic 12 — the retail attention-shake used by Amazon / Shopify / Etsy default themes to break tunnel vision on a static product page. Disabled state feedback: topic 14 — a soft shake when a user clicks a disabled button, communicating "this is intentionally blocked" better than a static grey pill. Notification bell arriving: topic 15 — the SaaS / Slack / Discord / GitHub pattern where a bell wiggles when a new notification lands, drawing eye to the badge count. Shopping cart add-to-cart: topic 16 — the e-commerce affordance that confirms the item was added AND says "go check your cart." Lock icon wrong password: topic 17 — a small icon shake in the auth field, complements the form shake above. Alert badge / notification count: topic 19 — periodic gentle shake to draw attention without spamming motion. Heart / like button: topic 20 — the Instagram / Twitter / TikTok double-tap shake-and-pulse combo. Trash icon drag-over: topic 21 — the drag-and-drop trash affordance shake, signalling the drop target is active. Continuous gentle wiggle: topic 26 — decorative onboarding pointers, notification-empty state loading, error-empty state. Elastic spring shake: topic 27 — modern linear() spring easing (Chrome 113+, Safari 17.2+, Firefox 112+) for the newest tier of natural motion. Web game hit damage: topics 29–30 — HTML5 game UI-layer damage cues (health bar hit, boss attack), works alongside canvas / WebGL for the actual gameplay. Rule of thumb: always pair shake with a non-motion cue — color, border, icon, text — so the state reads without motion perception (see the accessibility FAQ below)..shake class → let it play → in the animationend event listener, remove the class. On next invalid submit, add the class again → animation fires cleanly. Code: function shakeIt(el) { if (el.classList.contains('shake')) return; el.classList.add('shake'); el.addEventListener('animationend', () => el.classList.remove('shake'), { once: true }); }. The { once: true } auto-cleans the listener; the initial guard prevents re-adds mid-shake. This is what every demo in topics 01–08 uses. 2. Modern :user-invalid pseudo-class (Chrome 119+, Safari 17+, Firefox 88+): CSS can now trigger the shake purely on the browser's native validation state — input:user-invalid { animation: shake 0.5s both; }. Each time the browser flips :user-invalid from off → on (i.e., the user tries to submit and validation fails), the animation replays automatically. Zero JavaScript. Fallback to approach 1 for older browsers via @supports (selector(:user-invalid)). 3. The void-offsetWidth reflow hack (DEPRECATED — do not use): el.classList.remove('shake'); void el.offsetWidth; el.classList.add('shake');. The void el.offsetWidth forces a synchronous layout reflow, which resets the animation state. It works, but it's a 10–30ms main-thread block per shake — measurable INP hit, especially on mid-tier Android. Every 2019-era tutorial recommends this pattern; it's since been superseded by animationend cleanup (no reflow, no INP cost) and :user-invalid (no JS at all). Bonus gotcha: if your form uses React / Vue / Svelte, the framework may batch state updates and skip the class-toggle. Add the class via a ref imperatively OR use flushSync (React 18+) to force the intermediate render.prefers-reduced-motion: reduce is set. Every demo in this collection ships @media (prefers-reduced-motion: reduce) { .shake { animation: none; } } AND a non-motion fallback — a solid red border + inline error message so the state still reads. 2. Motion is never the sole signal (WCAG 1.4.1 Use of Color, applied to motion): a form field shake alone is not a valid error indication. The user with reduced motion disabled sees nothing. Every demo pairs the shake with (a) a color change (usually red-500), (b) a border-style change (dashed or thicker), (c) an inline text error (aria-live="polite" region), AND (d) the aria-invalid="true" attribute for screen readers. Removing any one weakens the accessibility signal. 3. Semantic HTML for form validation: use <form novalidate> so you can control validation timing (the browser's default HTML5 validation is jarring and hard to style). Every input has required / type="email" etc. so :user-invalid and aria-invalid work. Error text sits in an aria-live="polite" region below the field so screen readers announce it without interrupting the user mid-typing. 4. Regulatory frameworks: US ADA + Section 508 + EU EAA (enforcement began June 2025) + Canada ACA + UK Equality Act 2010 + Australian DDA all require WCAG 2.2 AA compliance for public-facing digital products. Lawsuits under all six have specifically cited animations that didn't honour prefers-reduced-motion. Domino's Pizza (2019, US Supreme Court denied cert) and Beyoncé.com (2019) both involved this exact issue. Test in browser: Chrome DevTools > Rendering panel > "Emulate CSS media feature prefers-reduced-motion" > reduce. Then submit every form on the page — the shake should NOT fire, but the error state should still communicate..shake class matches roughly what this collection ships as topic 01, but the whole library ships together — you can't tree-shake a single animation. If you only need shake, this collection's ~40-line snippet is 99.5% smaller than the whole Animate.css bundle. Framer Motion (~60KB minified, ~5M weekly downloads): React-specific, declarative API. Shake via <motion.div animate={{ x: [0, -4, 4, -4, 4, 0] }} transition={{ duration: 0.5 }}>. Adds 60KB of runtime just for shake — worth it if you already use Framer Motion for other animations, wasteful otherwise. GSAP (~23KB core, free): general-purpose animation engine. GSAP's shake is via gsap.to(el, { x: 'random(-4, 4)', duration: 0.05, repeat: 10, yoyo: true }). More flexible but same story — 23KB core + GSAP-specific API to learn. anime.js (~17KB): shake via anime({ targets: el, translateX: [-4, 4, -4, 4, 0], duration: 500 }). Same tradeoff. react-shake / shake.js / wobble.js: single-purpose libraries. Even at 2–5KB, they're dependencies to update + audit for CVEs. Web Animations API (WAAPI, native): el.animate([{ transform: 'translateX(-4px)' }, { transform: 'translateX(4px)' }, { transform: 'translateX(0)' }], { duration: 500 }). Zero deps, works in every modern browser, but requires JS to trigger. What this collection gives you: pure CSS keyframes + optional animationend cleanup JS (10 lines). Zero bundle cost, zero runtime dependency, zero CVE surface, faster First Contentful Paint. For the 90% of shake use cases (form errors, button attention, notification bell, cart wiggle), the pure-CSS approach wins on every Core Web Vitals dimension. Reach for a library only if you (a) already use it for other animations, (b) need runtime-computed shake intensity (dynamic amplitude based on user data), or (c) need chained shake sequences with precise callbacks.animate-shake on the element. keyframes: { shake: { '10%, 90%': { transform: 'translateX(-1px)' }, '20%, 80%': { transform: 'translateX(2px)' }, '30%, 50%, 70%': { transform: 'translateX(-4px)' }, '40%, 60%': { transform: 'translateX(4px)' } } }, animation: { shake: 'shake 0.5s cubic-bezier(.36,.07,.19,.97) both' }. In Tailwind v4 (CSS-first config): @keyframes shake { ... } @utility animate-shake { animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both; }. Every demo in this collection includes the equivalent Tailwind arbitrary-utility recipe in its customization field. shadcn/ui: does not ship shake; the community pattern is to write the keyframes in your global CSS + add the class conditionally in the component. Chakra UI: has <Box animation={${shakeAnim} 0.5s}> with a keyframes helper — closest to first-class support but you still author the shake yourself. MUI / Material UI: sx={{ animation: 'shake 0.5s' }} + a global keyframes definition. Same as Chakra. Ant Design / Mantine / HeadlessUI / Radix: no built-in shake; author your own or copy from this collection. Recommendation: the shake is small enough (~40 lines including the reduced-motion guard) that authoring it once in your global CSS + calling it via any of the above libraries is faster than reaching for a shake-specific dependency. Every demo in this collection is drop-in ready — copy the css tab into your global stylesheet, copy the class name into your framework of choice, done.<form novalidate> — the novalidate attribute disables the browser's default validation UI so you can control the timing (browser default fires on blur, which is jarring during typing). 2. Input with real validation attributes: <input type="email" required aria-invalid="false" aria-describedby="email-error">. The type="email" and required attributes enable the browser's constraint validation API — you can then use input.validity.valid in JS and input:user-invalid in CSS. The aria-invalid starts "false"; JS flips it to "true" on validation failure so screen readers announce the error state. The aria-describedby links to the error message container. 3. Error message region: <div id="email-error" role="alert" aria-live="polite"></div>. The role="alert" + aria-live="polite" combination announces the error to screen readers without interrupting the user mid-typing. Populate the div's textContent with the human-readable error on validation failure; clear it on success. 4. Label: real <label for="email">Email address</label> — never rely on placeholder alone; the placeholder disappears on focus and screen readers don't announce it as reliably. 5. Submit handler: form.addEventListener('submit', e => { e.preventDefault(); if (!form.checkValidity()) { shakeInvalidFields(form); return; } submitForm(); });. The checkValidity() call runs constraint validation without showing the browser's default popup. shakeInvalidFields iterates form.querySelectorAll(':invalid'), sets aria-invalid="true", updates the error message, and adds the shake class with the animationend-cleanup pattern from the re-trigger FAQ above. 6. Success reset: on successful submit, clear aria-invalid, empty the error region, and remove the shake class. Regulatory context: this exact pattern is what ADA / Section 508 / EU EAA / WCAG 2.2 AA audits expect. Skip any step and you fail on WCAG 4.1.3 (Status Messages) or 3.3.1 (Error Identification) or 1.4.1 (Use of Color). Every form demo in this collection ships all six steps.<div class="hud"> costs zero canvas render time, runs on the compositor thread (no game loop stall), and is trivially disabled via prefers-reduced-motion. Pattern: @keyframes hit-shake { 0%, 100% { transform: translateX(0); } 20%, 60% { transform: translateX(-8px); } 40%, 80% { transform: translateX(8px); } } applied on hit-detection via a class toggle. Canvas / WebGL wins for camera shake: when the entire game viewport needs to shake in response to a big attack, explosion, or environmental event, you need to shake the render context itself — otherwise UI-layer shake looks disconnected from the gameplay it's reacting to. In canvas: ctx.translate(shakeX, shakeY) inside the game loop, with shakeX and shakeY decayed each frame. In WebGL (Three.js / Babylon / PIXI): apply the shake to the camera transform. Combining both: the strongest game-feel combines a subtle camera shake in canvas with a punchy CSS shake on the HUD elements — the two motions layer to create the "something big just happened" feeling without either being overwhelming alone. Performance ceiling: CSS shake on 5+ HUD elements simultaneously stays at 60fps on mid-tier Android. Canvas shake at 60fps depends entirely on your render complexity — profile with Chrome DevTools Performance panel. Reduced motion: game camera shake is arguably essential to gameplay (damage feedback), so prefers-reduced-motion should either scale it down to 20% amplitude OR replace it with a screen-flash / border-flash + damage-number pop-in. Never silently drop the damage cue — the user still needs to know they took a hit. Framework libraries: Phaser 3 has camera.shake(duration, intensity); PIXI.js has @pixi/filter-shockwave; Three.js requires manual camera shake. All good for the gameplay layer. This collection covers the CSS/UI-layer half.20 hand-coded CSS animated buttons — neon glow, ripple, 3D press, liquid fill, jelly bounce, shine sweep, animated border, moving gradient CTA, text flip, submit success state, add-to-cart progress, download icon, hamburger-to-close, toggle switch, loading spinner inside button, next/prev arrow nav, and ghost button background reveal. Half pure CSS, half lightweight JS for production interactions.
11 pure CSS animation-timeline demos built on the W3C scroll() and view() spec — reading progress bar, view-timeline fade & reveal, shrinking sticky header, stacking cards, horizontal scroll section, parallax hero, container shadow indicators, reverse-scroll columns, text scrim reveal, cover card to sticky header, and image zoom / clip-path wipe. Zero JS, off the main thread, Core Web Vitals safe. Copy-paste ready.
25 hand-coded CSS background animations for SaaS hero sections, developer tool documentation, agency portfolios, gaming and Web3 landing pages, seasonal e-commerce campaigns, and dark-mode dashboards. Covers full-screen gradient shifts, animated mesh gradients, aurora borealis glow, cursor spotlight follow, interactive water ripples, dot grid and diagonal stripe patterns, SVG grain and noise texture overlays, topographic contour lines, layered SVG waves, plasma and lava lamp fluids, bokeh light blur, particle constellation networks, matrix digital rain, synthwave 3D perspective grids, starfield warp speed, CRT scanline overlays, fog and mist drift, morphing organic blobs, floating glassmorphism orbs, halftone dot patterns, starry night skies, floating geometric shapes, rising bubbles, and falling snow. 20 are pure CSS with zero JavaScript; the 5 that need it use vanilla canvas or pointer tracking with no dependencies. Every background animates compositor-only properties so it never blocks the main thread, is scoped under a .bga-NN prefix for no-collision pasting, guards prefers-reduced-motion, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.