30 CSS Shake Animations06 / 30

CSS + JSMIT licensed

CSS Shake Text on Error

Shaking the message instead of the box: an inline field error that drops in and rattles exactly once as it lands (so the eye snaps to the words), and a full 'ACCESS DENIED' terminal stamp with a per-word tremble that intensifies on hover.

Published

Live Demo
Try it

The code

<div class="shk-06-group">
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400..800&display=swap" rel="stylesheet">
<section class="shk-06a" aria-label="Inline error text that shakes as it appears">
  <div class="shk-06a__card">
    <h2 class="shk-06a__title">Transfer amount</h2>
    <form class="shk-06a__form" id="shk-06a-form" novalidate>
      <div class="shk-06a__inwrap"><span class="shk-06a__cur" aria-hidden="true">$</span><input class="shk-06a__input" id="shk-06a-amt" type="text" inputmode="decimal" placeholder="0.00" aria-describedby="shk-06a-err" autocomplete="off"></div>
      <button class="shk-06a__btn" type="submit">Send money</button>
    </form>
    <p class="shk-06a__err" id="shk-06a-err" aria-live="assertive"></p>
    <p class="shk-06a__hint" aria-hidden="true">balance is $250 — try sending 900, the words do the recoil</p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700;800&display=swap" rel="stylesheet">
<section class="shk-06b" aria-label="Access denied stamp with trembling words">
  <div class="shk-06b__frame">
    <p class="shk-06b__code">ERR 403 · CLEARANCE LEVEL INSUFFICIENT</p>
    <h2 class="shk-06b__stamp" aria-label="Access denied"><span aria-hidden="true" style="--d:0s">ACCESS</span><span aria-hidden="true" style="--d:.09s">DENIED</span></h2>
    <p class="shk-06b__foot">hover the stamp — the tremble doubles down</p>
  </div>
</section>
</div>
.shk-06-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.shk-06-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.shk-06a,
.shk-06a *,
.shk-06a *::before,
.shk-06a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.shk-06a {
  --err: oklch(0.62 0.21 25);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(180deg,#f7f7f5,#ecece8);
  font-family: 'Manrope','Segoe UI',system-ui,sans-serif;
}

.shk-06a__card {
  width: min(420px,100%);
  padding: 34px;
  border-radius: 20px;
  background: #fff;
  border: 1px solid rgba(25,28,35,.1);
  box-shadow: 0 30px 70px -40px rgba(25,28,35,.45);
}

.shk-06a__title {
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -.01em;
  color: #191c23;
}

.shk-06a__form {
  margin-top: 18px;
  display: grid;
  gap: 12px;
}

.shk-06a__inwrap {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 16px;
  border: 1.5px solid rgba(25,28,35,.15);
  border-radius: 13px;
  transition: border-color .2s;
}

.shk-06a__inwrap:focus-within {
  border-color: #191c23;
}

.shk-06a__inwrap.bad {
  border-color: var(--err);
}

.shk-06a__cur {
  font-size: 22px;
  font-weight: 700;
  color: rgba(25,28,35,.35);
}

.shk-06a__input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  font: inherit;
  font-size: 26px;
  font-weight: 700;
  color: #191c23;
  padding: 10px 4px;
  background: transparent;
}

.shk-06a__input::placeholder {
  color: rgba(25,28,35,.25);
}

.shk-06a__btn {
  padding: 14px;
  border: 0;
  border-radius: 12px;
  font: inherit;
  font-size: 15px;
  font-weight: 800;
  color: #fff;
  background: #191c23;
  cursor: pointer;
  transition: transform .15s,background .2s;
}

.shk-06a__btn:hover {
  background: #2c313d;
}

.shk-06a__btn:active {
  transform: scale(.98);
}

.shk-06a__err {
  min-height: 22px;
  margin-top: 12px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--err);
  display: flex;
  flex-wrap: wrap;
  gap: .28em;
}

.shk-06a__err span {
  display: inline-block;
  animation: shk-06a-drop .6s cubic-bezier(.36,.07,.19,.97) both;
  animation-delay: var(--d,0s);
}

.shk-06a__hint {
  margin-top: 14px;
  font-size: 11.5px;
  font-weight: 500;
  color: rgba(25,28,35,.45);
}

@keyframes shk-06a-drop {
  0% {
    opacity: 0;
    translate: 0 -7px;
  }

  25% {
    opacity: 1;
    translate: 0 0;
  }

  40% {
    translate: -5px 0;
  }

  55% {
    translate: 4px 0;
  }

  70% {
    translate: -2px 0;
  }

  85% {
    translate: 1px 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-06a__err span {
    animation: shk-06a-fade .3s ease both;
  }
}

@keyframes shk-06a-fade {
  from {
    opacity: 0;
  }
}

.shk-06b,
.shk-06b *,
.shk-06b *::before,
.shk-06b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.shk-06b {
  --red: oklch(0.64 0.24 27);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: radial-gradient(80% 90% at 50% 40%,#170c0d,#0a0607 70%);
  font-family: 'JetBrains Mono',ui-monospace,monospace;
}

.shk-06b__frame {
  text-align: center;
  padding: 44px 40px;
  border: 1.5px dashed oklch(0.64 0.24 27 / .45);
  border-radius: 6px;
  background: oklch(0.64 0.24 27 / .04);
}

.shk-06b__code {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .3em;
  color: oklch(0.64 0.24 27 / .75);
}

.shk-06b__stamp {
  margin-top: 18px;
  display: flex;
  gap: .4em;
  justify-content: center;
  flex-wrap: wrap;
  font-size: clamp(38px,8vw,84px);
  font-weight: 800;
  letter-spacing: .04em;
  color: var(--red);
  text-shadow: 0 0 34px oklch(0.64 0.24 27 / .5);
}

.shk-06b__stamp span {
  display: inline-block;
  animation: shk-06b-tremble .38s steps(2,jump-none) infinite;
  animation-delay: var(--d);
}

.shk-06b__frame:hover .shk-06b__stamp span {
  animation-duration: .14s;
  text-shadow: 2px 0 0 oklch(0.7 0.2 25 / .6),-2px 0 0 oklch(0.75 0.12 220 / .6);
}

.shk-06b__foot {
  margin-top: 22px;
  font-size: 11px;
  letter-spacing: .12em;
  color: rgba(255,220,220,.35);
}

@keyframes shk-06b-tremble {
  0% {
    translate: -1.5px .5px;
    rotate: -.25deg;
  }

  50% {
    translate: 1.5px -.5px;
    rotate: .25deg;
  }

  100% {
    translate: -1.5px .5px;
    rotate: -.25deg;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-06b__stamp span,
    .shk-06b__frame:hover .shk-06b__stamp span {
    animation: none;
  }
}
(function(){
  var form=document.getElementById('shk-06a-form'); if(!form) return;
  var amt=document.getElementById('shk-06a-amt'),err=document.getElementById('shk-06a-err'),wrap=form.querySelector('.shk-06a__inwrap');
  form.addEventListener('submit',function(e){
    e.preventDefault();
    var v=parseFloat(amt.value||'0');
    var msg=null;
    if(!amt.value.trim()) msg='Enter an amount first.';
    else if(isNaN(v)) msg='Numbers only — that isn\u2019t an amount.';
    else if(v>250) msg='Insufficient funds: your balance is $250.00.';
    wrap.classList.toggle('bad',!!msg);
    err.textContent='';
    if(!msg){ err.innerHTML='<span style="color:oklch(0.55 0.14 155)">Sent. New balance: $'+(250-v).toFixed(2)+'</span>'; return; }
    // Each word its own span with a slightly irregular delay — the rattle feels organic.
    msg.split(' ').forEach(function(w,i){
      var s=document.createElement('span'); s.textContent=w;
      s.style.setProperty('--d',(i*0.045+(i%2?0.02:0)).toFixed(3)+'s');
      err.appendChild(s);
    });
  });
})();

/* No JavaScript — two words tremble on a steps(2) micro-loop, phase-shifted by --d so they never move in lockstep; hover shortens animation-duration (same frames, 2.7× the frequency) and adds a chromatic text-shadow. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Shake Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Shake Text on Error
Source: https://codefronts.com/motion/css-shake-animation/css-shake-text-on-error/

Shaking the message instead of the box: an inline field error that drops in and rattles exactly once as it lands (so the eye snaps to the words), and a full 'ACCESS DENIED' terminal stamp with a per-word tremble that intensifies on hover.
## HTML
```html
<div class="shk-06-group">
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400..800&display=swap" rel="stylesheet">
<section class="shk-06a" aria-label="Inline error text that shakes as it appears">
  <div class="shk-06a__card">
    <h2 class="shk-06a__title">Transfer amount</h2>
    <form class="shk-06a__form" id="shk-06a-form" novalidate>
      <div class="shk-06a__inwrap"><span class="shk-06a__cur" aria-hidden="true">$</span><input class="shk-06a__input" id="shk-06a-amt" type="text" inputmode="decimal" placeholder="0.00" aria-describedby="shk-06a-err" autocomplete="off"></div>
      <button class="shk-06a__btn" type="submit">Send money</button>
    </form>
    <p class="shk-06a__err" id="shk-06a-err" aria-live="assertive"></p>
    <p class="shk-06a__hint" aria-hidden="true">balance is $250 — try sending 900, the words do the recoil</p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700;800&display=swap" rel="stylesheet">
<section class="shk-06b" aria-label="Access denied stamp with trembling words">
  <div class="shk-06b__frame">
    <p class="shk-06b__code">ERR 403 · CLEARANCE LEVEL INSUFFICIENT</p>
    <h2 class="shk-06b__stamp" aria-label="Access denied"><span aria-hidden="true" style="--d:0s">ACCESS</span><span aria-hidden="true" style="--d:.09s">DENIED</span></h2>
    <p class="shk-06b__foot">hover the stamp — the tremble doubles down</p>
  </div>
</section>
</div>
```
## CSS
```css
.shk-06-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.shk-06-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.shk-06a,
.shk-06a *,
.shk-06a *::before,
.shk-06a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.shk-06a {
  --err: oklch(0.62 0.21 25);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(180deg,#f7f7f5,#ecece8);
  font-family: 'Manrope','Segoe UI',system-ui,sans-serif;
}

.shk-06a__card {
  width: min(420px,100%);
  padding: 34px;
  border-radius: 20px;
  background: #fff;
  border: 1px solid rgba(25,28,35,.1);
  box-shadow: 0 30px 70px -40px rgba(25,28,35,.45);
}

.shk-06a__title {
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -.01em;
  color: #191c23;
}

.shk-06a__form {
  margin-top: 18px;
  display: grid;
  gap: 12px;
}

.shk-06a__inwrap {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 16px;
  border: 1.5px solid rgba(25,28,35,.15);
  border-radius: 13px;
  transition: border-color .2s;
}

.shk-06a__inwrap:focus-within {
  border-color: #191c23;
}

.shk-06a__inwrap.bad {
  border-color: var(--err);
}

.shk-06a__cur {
  font-size: 22px;
  font-weight: 700;
  color: rgba(25,28,35,.35);
}

.shk-06a__input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  font: inherit;
  font-size: 26px;
  font-weight: 700;
  color: #191c23;
  padding: 10px 4px;
  background: transparent;
}

.shk-06a__input::placeholder {
  color: rgba(25,28,35,.25);
}

.shk-06a__btn {
  padding: 14px;
  border: 0;
  border-radius: 12px;
  font: inherit;
  font-size: 15px;
  font-weight: 800;
  color: #fff;
  background: #191c23;
  cursor: pointer;
  transition: transform .15s,background .2s;
}

.shk-06a__btn:hover {
  background: #2c313d;
}

.shk-06a__btn:active {
  transform: scale(.98);
}

.shk-06a__err {
  min-height: 22px;
  margin-top: 12px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--err);
  display: flex;
  flex-wrap: wrap;
  gap: .28em;
}

.shk-06a__err span {
  display: inline-block;
  animation: shk-06a-drop .6s cubic-bezier(.36,.07,.19,.97) both;
  animation-delay: var(--d,0s);
}

.shk-06a__hint {
  margin-top: 14px;
  font-size: 11.5px;
  font-weight: 500;
  color: rgba(25,28,35,.45);
}

@keyframes shk-06a-drop {
  0% {
    opacity: 0;
    translate: 0 -7px;
  }

  25% {
    opacity: 1;
    translate: 0 0;
  }

  40% {
    translate: -5px 0;
  }

  55% {
    translate: 4px 0;
  }

  70% {
    translate: -2px 0;
  }

  85% {
    translate: 1px 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-06a__err span {
    animation: shk-06a-fade .3s ease both;
  }
}

@keyframes shk-06a-fade {
  from {
    opacity: 0;
  }
}

.shk-06b,
.shk-06b *,
.shk-06b *::before,
.shk-06b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.shk-06b {
  --red: oklch(0.64 0.24 27);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: radial-gradient(80% 90% at 50% 40%,#170c0d,#0a0607 70%);
  font-family: 'JetBrains Mono',ui-monospace,monospace;
}

.shk-06b__frame {
  text-align: center;
  padding: 44px 40px;
  border: 1.5px dashed oklch(0.64 0.24 27 / .45);
  border-radius: 6px;
  background: oklch(0.64 0.24 27 / .04);
}

.shk-06b__code {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .3em;
  color: oklch(0.64 0.24 27 / .75);
}

.shk-06b__stamp {
  margin-top: 18px;
  display: flex;
  gap: .4em;
  justify-content: center;
  flex-wrap: wrap;
  font-size: clamp(38px,8vw,84px);
  font-weight: 800;
  letter-spacing: .04em;
  color: var(--red);
  text-shadow: 0 0 34px oklch(0.64 0.24 27 / .5);
}

.shk-06b__stamp span {
  display: inline-block;
  animation: shk-06b-tremble .38s steps(2,jump-none) infinite;
  animation-delay: var(--d);
}

.shk-06b__frame:hover .shk-06b__stamp span {
  animation-duration: .14s;
  text-shadow: 2px 0 0 oklch(0.7 0.2 25 / .6),-2px 0 0 oklch(0.75 0.12 220 / .6);
}

.shk-06b__foot {
  margin-top: 22px;
  font-size: 11px;
  letter-spacing: .12em;
  color: rgba(255,220,220,.35);
}

@keyframes shk-06b-tremble {
  0% {
    translate: -1.5px .5px;
    rotate: -.25deg;
  }

  50% {
    translate: 1.5px -.5px;
    rotate: .25deg;
  }

  100% {
    translate: -1.5px .5px;
    rotate: -.25deg;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shk-06b__stamp span,
    .shk-06b__frame:hover .shk-06b__stamp span {
    animation: none;
  }
}
```

## JavaScript
```js
(function(){
  var form=document.getElementById('shk-06a-form'); if(!form) return;
  var amt=document.getElementById('shk-06a-amt'),err=document.getElementById('shk-06a-err'),wrap=form.querySelector('.shk-06a__inwrap');
  form.addEventListener('submit',function(e){
    e.preventDefault();
    var v=parseFloat(amt.value||'0');
    var msg=null;
    if(!amt.value.trim()) msg='Enter an amount first.';
    else if(isNaN(v)) msg='Numbers only — that isn\u2019t an amount.';
    else if(v>250) msg='Insufficient funds: your balance is $250.00.';
    wrap.classList.toggle('bad',!!msg);
    err.textContent='';
    if(!msg){ err.innerHTML='<span style="color:oklch(0.55 0.14 155)">Sent. New balance: $'+(250-v).toFixed(2)+'</span>'; return; }
    // Each word its own span with a slightly irregular delay — the rattle feels organic.
    msg.split(' ').forEach(function(w,i){
      var s=document.createElement('span'); s.textContent=w;
      s.style.setProperty('--d',(i*0.045+(i%2?0.02:0)).toFixed(3)+'s');
      err.appendChild(s);
    });
  });
})();

/* No JavaScript — two words tremble on a steps(2) micro-loop, phase-shifted by --d so they never move in lockstep; hover shortens animation-duration (same frames, 2.7× the frequency) and adds a chromatic text-shadow. */
```

How this works

When text appears AND shakes, run both in one animation so entrance and rattle can't drift apart:

@keyframes drop-shake{
  0%{opacity:0;translate:0 -6px}
  25%{opacity:1;translate:0 0}
  40%{translate:-6px 0} 55%{translate:5px 0}
  70%{translate:-3px 0} 85%{translate:1px 0}
}

The first quarter is the entrance (fade + drop), the rest is the decay shake — one animationend, one cleanup. For display-size text, shake per word with tiny random-feeling animation-delay offsets; whole-block shakes look like a browser bug at 80px font size, but words trembling slightly out of phase read as energy.

Make it yours

  • Keep inline error text shakes small (≤6px) — text is harder to read in motion than a box.
  • Stagger word delays by 40–90ms non-uniformly (0, .07s, .04s…) so the tremble feels organic, not choreographed.
  • For dark 'denied' screens, add a 1px red/cyan text-shadow pair during the shake for a broadcast-glitch edge.
  • Announce the error text via aria-live at the same moment the animation starts.

Gotchas — read before shipping

  • Never keep body-size text in permanent motion — vestibular trigger and a readability tax; shake once, then rest.
  • Shaking individual letters (vs words) breaks font ligatures and kerning pairs mid-animation; words are the safe unit.
  • If the message container has overflow:hidden for the reveal, give the shake room or the text clips at the swing extremes.
  • Set will-change only for the burst (or not at all) — dozens of permanently promoted text layers eat memory.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome all modern.

Plain transform keyframes — works everywhere including old Edge. The oklch() colors need Chrome 111 / Safari 15.4 / Firefox 113; supply hex fallbacks for legacy targets.

Techniques used in this demo

Search CodeFronts

Loading…