Last week we began dissecting the animations in Stripe Checkout and tackled the flap effect used to display content from radio buttons. This week we will look at the animation used during form validation:

This animation does an excellent job at letting the user know that the form submission did not pass validation and has form fields to correct. Typically for modal windows the standard user experience is to highlight the fields that have errors and have the user correct them. For small forms this works great, it can often become a goose chase in larger forms to determine whether or not there is an error or if the submission is in progress, and then locate the erroneous field. The shake animation does a great job at notifying the user it definitely did not go through and as Michaël Villar put it: “the form is basically shaking its head at you.”


The Stripe Checkout is a typical modal window overlay implemented in an iframe, we can have a fixed container as the background and the modal window sit in the center of the page. The container will need to apply the perspective property from last week. The perspective will push back the Z-plane and the animation will be more subtle. The actual modal will be put in the center of the page and adjusted with margin to have a symmetrically skewed animation.

.easing-box-container {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  -webkit-perspective: 500;
  background: rgba(0,0,0,0.6);

.easing-box {
  position: absolute;
  width: 300px;
  height: 500px;
  top: 50%;
  left: 50%;
  margin-top: -300px;
  margin-left: -150px;

static modal


Now that the modal window is positioned in the middle of the page, we want to apply the animation to it. Imagine shaking your head for a moment, the acceleration at the beginning and end are less than middle - this gives us a nice built-in easing function that we can already use: ease-in-out, the easing is in the beginning and the end. Shake your head a few times and end at the original position, notice that each shake the beginning and end positions were a few degrees closer to the original position. For this normal CSS transitions cannot be applied, we want to be able to control each state throughout the animation. In come CSS animation keyframes, this gives us a powerful syntax that will allow the expression of animation state at arbitrary points in the animation. Let's use these keyframes in the context of the ease-in-out easing function.

@-webkit-keyframes shakeAnimation {
    0% { /* initial */ }
    12.5% { /* ease in */ }
    37.5% { /* full-rate animation */ }
    62.5% { /* full-rate animation */ }
    87.5% { /* ease-out animation */ }
    100% { /* end */ }

Now that there is an animation defined, it can be referenced from a CSS rule with the animation-name property.

.shake {
  -webkit-animation-duration: 400ms;
  -webkit-animation-timing-function: ease-in-out;
  -webkit-animation-name: shakeAnimation;


All that is left is filling in the animation states at each keyframe. The six states we want to use throughout the animation are defined and we know the initial and end states already. The states in between will represent one end of the shake. The Y axis will be rotated and the X axis will be translated a little more to give the element the feel that it is shaking as it is rotating.

The Y rotations can be defined as going from max to min to max-1 to min-1 until the done state is reached. Starting from -5, this produces the rotation sequence (0, -5, 4, -3, 2, -1, 0). This is one too many states, we can discard -3 and put in -2 during the peak of the animation. The X translations will be 1px more than the degree and produces the sequence (0, -6, 5, -3, 2, 0). Let's put these two transform sequences into the keyframes defined above:

@-webkit-keyframes shakeAnimation {
  0% {
    -webkit-transform: translateX(0);

  12.5% {
    -webkit-transform: translateX(-6px) rotateY(-5deg);

  37.5% {
    -webkit-transform: translateX(5px) rotateY(4deg);

  62.5% {
    -webkit-transform: translateX(-3px) rotateY(-2deg);

  87.5% {
    -webkit-transform: translateX(2px) rotateY(1deg);

  100% {
    -webkit-transform: translateX(0);

Applying the Animation

The animation is triggered whenever the class .shake is added to an element. This will need to be attached to the button in the form and reapplied when the button is clicked.

var shakeBtnEl = document.getElementById("btn-shake");
shakeBtnEl.addEventListener("click", function(e) {
  var boxEl = document.getElementsByClassName("easing-box")[0];

There is a snag that when the CSS animations are applied and the class list is modified to re-add the .shake class, the animation will not be reapplied. The animation can be manually triggered by having the element reflow itself, this can be done by reassigning the offset width on the element.

boxEl.offsetWidth = boxEl.offsetWidth;

Putting this all together, here's the animation in action:

This is a part of the Dissecting Delight series, we initially will be reviewing the Stripe Checkout animations and hopefully many more great experiences. The code is available on Github here, please feel free to make poke me on Twitter if there's anything I can help with or correct!