Sid Ngeth's Blog A blog about anything (but mostly development)

form validation with jitter effects

Ever notice how form validation can feel… boring? You click submit, some fields turn red, maybe you get an error message. Functional, sure. But what if we could make invalid fields literally shake their heads at you?

try it out

Jitter Effect Preview

Fill out this form and try the validation buttons to see different jitter animations on required empty fields.

the problem with standard validation

HTML5 gives us built-in form validation with the required attribute and input types like email. But the default browser behavior is pretty bland. And if you add novalidate to your form (which many of us do for custom validation), you’re on your own for feedback.

<form id="testForm" novalidate>
  <input type="email" required>
</form>

That novalidate attribute tells the browser ā€œthanks but no thanks, I’ll handle validation myself.ā€ Which opens the door for more creative feedback…

enter jitter animations

Instead of just turning fields red, what if they physically reacted to being empty? Here’s a collection of CSS animations that give form fields personality:

@keyframes jitter-shake {
  0%, 100% { transform: translateX(0); }
  10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
  20%, 40%, 60%, 80% { transform: translateX(4px); }
}

@keyframes jitter-bounce {
  0%, 100% { transform: translateY(0); }
  25% { transform: translateY(-8px); }
  50% { transform: translateY(-4px); }
  75% { transform: translateY(-2px); }
}

@keyframes jitter-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}

@keyframes jitter-wobble {
  0%, 100% { transform: rotate(0deg); }
  25% { transform: rotate(-2deg); }
  50% { transform: rotate(2deg); }
  75% { transform: rotate(-1deg); }
}

Each animation has its own personality and color:

  • shake: the classic ā€œnopeā€ head shake (red #f7768e)
  • bounce: a gentle hop like ā€œhey, over here!ā€ (orange #ff9e64)
  • pulse: subtle breathing effect for a softer touch (purple #bb9af7)
  • wobble: playful rotation that feels less aggressive (pink #f7768e)

The colors are applied along with the animation class:

.jitter-shake {
    animation: jitter-shake 0.6s ease-in-out;
    border-color: #f7768e !important;
}

This means when an input gets the jitter effect, it not only animates but also changes its border color to match the animation’s personality. The !important ensures the color override takes precedence during the animation.

the validation logic

Here’s where it gets interesting. Instead of validating fields one by one, we collect all empty required fields and animate them simultaneously. The function gets called from the test buttons, each passing a different animation type:

<button type="button" onclick="validateForm('shake')">šŸ”ø Test Shake</button>
<button type="button" onclick="validateForm('bounce')">šŸ”¹ Test Bounce</button>
<button type="button" onclick="validateForm('pulse')">šŸ”ø Test Pulse</button>
<button type="button" onclick="validateForm('wobble')">šŸ”¹ Test Wobble</button>

And here’s the validation function itself:

function validateForm(animationType = 'shake') {
  const form = document.getElementById('testForm');
  const requiredInputs = form.querySelectorAll('input[required], textarea[required], select[required]');

  let emptyFields = [];
  let emptyInputs = [];

  // first pass: identify all empty fields
  requiredInputs.forEach(input => {
    if (isEmpty(input)) {
      emptyFields.push(input.name || input.id);
      emptyInputs.push(input);
    }
  });

  // second pass: jitter all empty fields simultaneously
  if (emptyInputs.length > 0) {
    emptyInputs.forEach(input => {
      jitterElement(input.id, animationType);
    });

    statusDiv.className = 'status error';
    statusDiv.textContent = `āŒ Please fill in: ${emptyFields.join(', ')}`;
    return false;
  }

  return true;
}

The two-pass approach means all invalid fields react at once, creating a unified ā€œthese need attentionā€ moment rather than a sequential cascade.

applying the effect

The jitter function handles the animation lifecycle cleanly:

function jitterElement(elementId, animationType = 'shake') {
  const element = document.getElementById(elementId);
  const className = `jitter-${animationType}`;

  // only remove classes if this element doesn't already have the target class
  if (!element.classList.contains(className)) {
    // remove any existing jitter classes
    element.classList.remove('jitter-shake', 'jitter-bounce', 'jitter-pulse', 'jitter-wobble');
  }

  // add the new jitter class
  element.classList.add(className);

  // remove after animation completes
  setTimeout(() => {
    element.classList.remove(className);
  }, 600);
}

subtle touches

You can also add jitter on blur for immediate feedback:

document.querySelectorAll('input[required]').forEach(input => {
  input.addEventListener('blur', function() {
    if (isEmpty(this)) {
      setTimeout(() => jitterElement(this.id, 'shake'), 100);
    }
  });
});

That 100ms delay prevents the animation from feeling too aggressive when users are just tabbing through fields.

The form submission also triggers validation:

document.getElementById('testForm').addEventListener('submit', function(e) {
  e.preventDefault();

  if (validateForm('shake')) {
    // form is valid, show success message
  }
});

So validation happens in three scenarios:

  1. When clicking any of the test buttons (with their specific animation)
  2. When blurring out of a required field that’s empty (always uses shake)
  3. When submitting the form (uses shake by default)

when to use what

Different animations work better for different contexts:

  • use shake for critical errors or final form submission
  • use bounce for friendly reminders
  • use pulse for subtle hints in longer forms
  • use wobble for playful interfaces or less serious applications

The key is matching the animation personality to your form’s context. A medical form probably wants subtle pulse effects. A game signup might embrace the full wobble.

performance notes

CSS transforms are GPU-accelerated, so these animations won’t cause layout thrashing. The 600ms duration is long enough to be noticeable but short enough to not feel sluggish. And since we’re using classes rather than inline styles, the browser can optimize the animations.

Form validation doesn’t have to be boring. Sometimes a little shake is all you need to make the experience memorable without being annoying.

comments powered by Disqus