SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/shake-animations

7 CSS Shake Animations

3 Pure CSS4 with JSPublished 2026-10-05Free · no sign-up

Seven shakes with a purpose: a sign-in card that shakes on a wrong password, a ringing notification bell, a call to action that wiggles now and then, nods for yes and shakes for no, icons that jiggle in edit mode, a phone that buzzes and one configurable shake at three strengths.

01. Card Shake on Wrong Password

ShakeError

The whole sign-in card shakes when the password is wrong, a familiar signal borrowed from phone lock screens. The error is also written below the field and announced through an alert region, so the shake is never the only feedback.

View code
<form class="sk1"><label for="sk1p">Password</label><input id="sk1p" type="password" autocomplete="current-password"><p role="alert"></p><button>Sign in</button></form>
.sk1 { display: grid; gap: 8px; width: 260px; max-width: 100%; padding: 20px; box-sizing: border-box; border: 1px solid #2a3350; border-radius: 16px; background: #141a2c; font-family: Inter, system-ui, sans-serif; color: #e2e8f0; }
.sk1.no { animation: sk1 .4s cubic-bezier(.36,.07,.19,.97); border-color: #f87171; }
@keyframes sk1 { 10%, 90% { transform: translateX(-2px); } 20%, 80% { transform: translateX(4px); } 30%, 50%, 70% { transform: translateX(-8px); } 40%, 60% { transform: translateX(8px); } }
.sk1 label { font-weight: 700; font-size: 14px; }
.sk1 input { min-width: 0; padding: 10px 12px; border: 1px solid #475569; border-radius: 8px; background: #0b1020; color: #fff; font: 15px Inter, system-ui, sans-serif; }
.sk1 p { margin: 0; min-height: 1.2em; color: #fca5a5; font-size: 13px; }
.sk1 button { padding: 11px; border: 0; border-radius: 8px; background: #6366f1; color: #fff; font: 700 14px Inter, system-ui, sans-serif; cursor: pointer; }
.sk1 :focus-visible { outline: 3px solid #a5b4fc; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .sk1.no { animation: none; } }
var f = document.querySelector('.sk1'), m = f.querySelector('p');
f.addEventListener('submit', function (e) { e.preventDefault(); f.classList.remove('no'); void f.offsetWidth; f.classList.add('no'); m.textContent = 'That password is not right. This demo never accepts one.'; f.querySelector('input').select(); });

02. Ringing Notification Bell

ShakeIcon

A bell icon swings from its top when a new notification arrives: a rotate animation with transform-origin at the top centre, damped over five swings. The button's label includes the unread count, which is what assistive technology relies on.

View code
<button class="sk2" aria-label="Notifications, 3 unread"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 16V11a6 6 0 1 1 12 0v5l2 2H4zM10 20a2 2 0 0 0 4 0"/></svg><span aria-hidden="true">3</span></button>
.sk2 { position: relative; display: grid; place-items: center; width: 56px; height: 56px; border: 1px solid #e4e7ec; border-radius: 16px; background: #fff; cursor: pointer; }
.sk2 svg { width: 26px; height: 26px; fill: none; stroke: #101828; stroke-width: 1.8; stroke-linejoin: round; transform-origin: 50% 8%; animation: sk2 2.4s ease-in-out infinite; }
.sk2 span { position: absolute; top: 8px; right: 8px; display: grid; place-items: center; min-width: 18px; height: 18px; border-radius: 9px; background: #d92d20; color: #fff; font: 800 11px Inter, system-ui, sans-serif; }
@keyframes sk2 { 0%, 50%, 100% { transform: rotate(0); } 5% { transform: rotate(16deg); } 10% { transform: rotate(-14deg); } 15% { transform: rotate(10deg); } 20% { transform: rotate(-6deg); } 25% { transform: rotate(3deg); } }
.sk2:focus-visible { outline: 3px solid #84adff; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .sk2 svg { animation: none; } }

03. Occasional Attention Wiggle

ShakeAttention

A call-to-action wiggles briefly every few seconds to catch the eye, then rests: the keyframes do their movement in the first 15 percent and stay still for the rest of a five-second loop. Hovering or focusing stops it, since the person has already found it.

View code
<button class="sk3">Claim your free month</button>
.sk3 { padding: 14px 26px; border: 0; border-radius: 12px; background: #f59e0b; color: #1c1917; font: 800 15px Inter, system-ui, sans-serif; cursor: pointer; animation: sk3 5s ease-in-out infinite; }
.sk3:hover, .sk3:focus-visible { animation-play-state: paused; }
@keyframes sk3 { 0%, 15%, 100% { transform: rotate(0); } 3% { transform: rotate(-4deg) scale(1.04); } 6% { transform: rotate(4deg) scale(1.04); } 9% { transform: rotate(-3deg) scale(1.02); } 12% { transform: rotate(2deg); } }
.sk3:focus-visible { outline: 3px solid #fcd34d; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .sk3 { animation: none; } }

04. Yes and No Head Shakes

ShakeFeedback

Two emoji-free faces: one nods vertically for yes and one shakes horizontally for no, the same body language people use. Press each button to replay, and the result is announced in text beside them.

View code
<div class="sk4"><button class="y" aria-label="Correct answer"><span aria-hidden="true">✓</span></button><button class="n" aria-label="Wrong answer"><span aria-hidden="true">✕</span></button><p aria-live="polite"></p></div>
.sk4 { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px; width: 280px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.sk4 button { display: grid; place-items: center; width: 64px; height: 64px; border: 0; border-radius: 50%; font-size: 26px; font-weight: 800; cursor: pointer; }
.sk4 .y { background: #dcfce7; color: #15803d; } .sk4 .n { background: #fee2e2; color: #b91c1c; }
.sk4 .y.go span { display: block; animation: sk4y .6s ease; } .sk4 .n.go span { display: block; animation: sk4n .5s ease; }
@keyframes sk4y { 25%, 75% { transform: translateY(6px); } 50% { transform: translateY(-4px); } }
@keyframes sk4n { 20%, 60% { transform: translateX(-7px); } 40%, 80% { transform: translateX(7px); } }
.sk4 p { flex-basis: 100%; margin: 0; text-align: center; color: #344054; font-size: 14px; min-height: 1.2em; }
.sk4 button:focus-visible { outline: 3px solid #84adff; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .sk4 span { animation: none !important; } }
var p = document.querySelector('.sk4 p');
document.querySelectorAll('.sk4 button').forEach(function (b) { b.onclick = function () { b.classList.remove('go'); void b.offsetWidth; b.classList.add('go'); p.textContent = b.classList.contains('y') ? 'Yes, that is right.' : 'No, try again.'; }; });

05. Edit Mode Icon Jiggle

ShakeEdit

App icons jiggle while in edit mode, as on phone home screens. Each icon has a slightly different animation delay so they do not move in unison, and the Edit button is a toggle with aria-pressed that turns the whole mode on and off.

View code
<div class="sk5"><button aria-pressed="false">Edit</button><ul><li style="--d:-.1s;--c:#f97316">M</li><li style="--d:-.25s;--c:#22c55e">C</li><li style="--d:-.05s;--c:#3b82f6">N</li><li style="--d:-.3s;--c:#e11d48">P</li></ul></div>
.sk5 { width: 250px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.sk5 button { margin-bottom: 14px; padding: 7px 14px; border: 1px solid #374151; border-radius: 999px; background: transparent; color: #e5e7eb; font: 600 13px Inter, system-ui, sans-serif; cursor: pointer; }
.sk5 [aria-pressed="true"] { background: #e5e7eb; color: #111827; }
.sk5 button:focus-visible { outline: 2px solid #93c5fd; outline-offset: 2px; }
.sk5 ul { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 0; padding: 0; list-style: none; }
.sk5 li { display: grid; place-items: center; aspect-ratio: 1; border-radius: 14px; background: var(--c); color: #fff; font-weight: 800; }
.sk5.edit li { animation: sk5 .3s ease-in-out infinite alternate; animation-delay: var(--d); }
@keyframes sk5 { from { transform: rotate(-2.5deg); } to { transform: rotate(2.5deg); } }
@media (prefers-reduced-motion: reduce) { .sk5.edit li { animation: none; outline: 2px dashed #fff; outline-offset: 3px; } }
var w = document.querySelector('.sk5'), b = w.querySelector('button');
b.onclick = function () { var on = w.classList.toggle('edit'); b.setAttribute('aria-pressed', on); b.textContent = on ? 'Done' : 'Edit'; };

06. Incoming Call Buzz

ShakeIcon

A phone icon buzzes in short bursts, combining a small rotation with a sideways shift so it reads as vibration rather than swinging. The answer and decline buttons below it are labelled, coloured and have text, because a call screen must be clear.

View code
<div class="sk6"><span class="ic" aria-hidden="true">📞</span><p>Mum is calling</p><div><button class="no">Decline</button><button class="ok">Answer</button></div></div>
.sk6 { display: grid; justify-items: center; gap: 6px; width: 240px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.sk6 .ic { display: grid; place-items: center; width: 64px; height: 64px; border-radius: 50%; background: #dcfce7; font-size: 30px; animation: sk6 1.6s linear infinite; }
@keyframes sk6 { 0%, 40%, 100% { transform: none; } 5%, 15%, 25%, 35% { transform: translateX(-2px) rotate(-8deg); } 10%, 20%, 30% { transform: translateX(2px) rotate(8deg); } }
.sk6 p { margin: 4px 0 8px; color: #101828; font-weight: 700; }
.sk6 div { display: flex; gap: 10px; }
.sk6 button { padding: 10px 18px; border: 0; border-radius: 999px; color: #fff; font: 700 14px Inter, system-ui, sans-serif; cursor: pointer; }
.sk6 .no { background: #b42318; } .sk6 .ok { background: #067647; }
.sk6 button:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .sk6 .ic { animation: none; } }

07. Three Shake Strengths

ShakeKeyframes

Subtle, medium and strong shakes side by side, so you can pick the intensity that suits the message. They share one set of keyframes that read the distance from a custom property, which shows how to make one animation configurable.

View code
<div class="sk7"><button style="--a:2px">Subtle</button><button style="--a:6px">Medium</button><button style="--a:12px">Strong</button></div>
.sk7 { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; box-sizing: border-box; max-width: 100%; }
.sk7 button { padding: 12px 18px; border: 1px solid #3a4670; border-radius: 10px; background: #1b2238; color: #e2e8f0; font: 600 14px Inter, system-ui, sans-serif; cursor: pointer; }
.sk7 button.go { animation: sk7 .45s ease; }
@keyframes sk7 { 20%, 60% { transform: translateX(calc(var(--a) * -1)); } 40%, 80% { transform: translateX(var(--a)); } }
.sk7 button:focus-visible { outline: 2px solid #818cf8; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .sk7 button.go { animation: none; } }
document.querySelectorAll('.sk7 button').forEach(function (b) { b.onclick = function () { b.classList.remove('go'); void b.offsetWidth; b.classList.add('go'); }; });

About these CSS Shake Animations

Shake means no

A sideways shake reads as a refusal, so use it for errors and wrong answers, and a vertical nod for success. Pair it with a written message every time; the motion alone tells a screen reader user nothing.

Replay with a reflow

To run the same animation again, remove the class, force a reflow by reading offsetWidth, then add the class back. Without the reflow the browser sees no change and does nothing.

Keep it short

Error shakes should last about 300 to 500ms with small distances, around 4 to 10 pixels. Anything longer feels like the page is broken.

Attention without nagging

A looping wiggle should spend most of its cycle at rest and stop on hover or focus. Under prefers-reduced-motion every shake here is switched off and the colour or text change remains.

Frequently asked questions

Can I change the shake distance?

Yes. The last demo reads it from a custom property, so one keyframe set serves all three strengths.

Will a shake trigger motion sickness?

Short, small shakes rarely do, but they are still disabled for people who ask for reduced motion.

Do I need a library?

No. Each shake is a few lines of @keyframes with transform.