This article is Part 2 in a 3-Part series.

We’re going to introduce animations into the Allstar Jamstack, beginning with the holy grail of web animations: Animated page transitions. Source code available in the foot notes.

yarn add turbolinks-animate

First, the markup

Modify the default layout with an animatable content wrapper like so:-

--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -8,6 +8,8 @@
     <link rel="stylesheet" href="{{ "/dist/main.css" | relative_url }}">
-    {{ content}}
+    <main class='turbolinks-animate'>
+      {{ content}}
+    </main>

This is so that we don’t trigger animations or transitions on the body tag, since this is not advised.

Next, modify the index page:-

 --- a/index.markdown
+++ b/index.markdown
@@ -4,7 +4,13 @@

 layout: home
-<div data-controller="hello">
-  <input data-target="" type="text">
-  <button data-action="click->hello#greet">Greet</button>
+<div id='home'>
+  <h1>Home</h1>
+  <a href='/transition'>Transition</a>
+  <div data-controller="hello">
+    <input data-target="" type="text">
+    <button data-action="click->hello#greet">Greet</button>
+  </div>

Now add a new page that we will transition to:-

layout: home

<div id='transition'>
    <a href='/'>Home</a>

Next, the CSS with Animate.css

Modify the CSS entrypoint file with the following:-

--- a/src/main.css
+++ b/src/main.css
@@ -1,4 +1,5 @@
 @import 'tailwindcss';
+@import '~turbolinks-animate/~animate.css/animate.css';
 @import './sass/main.scss';

 body {

Some things to note here. First, the tildes (~) preceding paths, is effectively a shorthand for ./node_modules, it lets webpack know that the source for this is a node module in node_modules. Secondly, note that you can nest these shorthand ~’s when using @import so you can get to nested node module dependencies!

Finally, the JS

Turbolinks provides two events that we need to leverage in order to make animated page transitions work, turbolinks:load and turbolinks:before-visit. The following change will handle the full transition by leveraging both Turbolinks and Turbolinks Animate:-

--- a/src/index.js
+++ b/src/index.js
@@ -11,4 +11,30 @@ application.load(definitionsFromContext(context))
 import Turbolinks from "turbolinks"

+import "turbolinks-animate";
 import './main.css';
+const fadeInTime = '0.4s'
+const fadeOutTime= '0.3s'
+document.addEventListener( 'turbolinks:load', function() {
+  TurbolinksAnimate.init({ duration: fadeInTime, animation: 'fadein', element: document.querySelector('main.turbolinks-animate') });
+document.addEventListener( 'turbolinks:before-visit', function(e) {
+  let animatedMain = document.querySelector('main.turbolinks-animate')
+  if(!animatedMain.classList.contains('transition-out')) {
+    animatedMain.classList.add('transition-out')
+    TurbolinksAnimate.init({ duration: fadeOutTime, animation: 'fadeout', element: document.querySelector('main.turbolinks-animate') });
+    setTimeout(function() {
+      Turbolinks.visit(;
+    }, 400);
+    e.preventDefault();
+  }

Since all the Animate.css CSS rules are being applied dynamically by Turbolinks Animate, PurgeCSS will not be able to pick up on them to optimise the bundle and will remove the whole lot when we bundle this in production mode. So we need to add the JS bundle to our PurgeCSS config, like so:-

--- a/purgecss.config.js
+++ b/purgecss.config.js
@@ -1,6 +1,6 @@
 module.exports = {
   // These are the files that Purgecss will search through
-  content: ["./_site/**/*.html"],
+  content: ["./_site/**/*.html", "./_site/**/*.js"],

   // These are the stylesheets that will be subjected to the purge
   css: ["./_site/dist/main.css"],

And that’s it! You can build in development or production mode safely!

The source code for this tutorial is available here.