Tailwind Enter/Leave Transition Effects with Stimulus.js
Tags: tailwindcss, animation, stimulus, rails
Published: 2020-09-08 21:55:59

---
title: Tailwind Enter/Leave Transition Effects with Stimulus.js
published: true
description: 
tags: tailwind, animation, stimulusjs, rails
//cover_image: https://direct_url_to_image.jpg
---

As a fan of TailwindUI, I was disappointed to find it impossible to support the suggested enter/leave without using Apline.js or Vue.js. The tailwind team even built their own transition component for react support. Working with Ruby on Rails and Stimulus.js I wanted to keep the project close to "core" rails. I didn't feel including Alpine.js or switching to Vue.js just for transitions was optimal. For what it's worth I think both Alpine and Vue are awesome!

Initially, I didn't bother trying. A little bit of research and a few youtube videos later I came up short. I settled on simply hiding/showing the element or applying a generic animation from something such as animate.css. But I just couldn't accept that it was impossible to implement. Plus it didn't look as nice. I dug a little deeper and came across this [amazing article](https://sebastiandedeyne.com/javascript-framework-diet/enter-leave-transitions/). I highly recommend reading it to understand how the transitions are implemented. I learned a lot and give the author a lot of credit!

I was able to take the lessons from the article and abstract it into a handy little npm package [el-transition](https://www.npmjs.com/package/el-transition). As I write this `el-transition` is hot off the press. Please review and contribute! 
{% github mmccall10/el-transition no-readme%}
We will use this package to implement our TailwindUI transition effects!

For this example let's build a dropdown component. You can find the tailwind markup here [here](https://tailwindui.com/components/application-ui/elements/dropdowns).

Before we get started install the `el-transition` package.
```bash
$ yarn add el-transition
```

### Time to Code
The following is a direct copy of the dropdown component from TailwindUI's library. Notice the comment specifying the suggested enter/leave transition classes. 

```html
<div class="relative inline-block text-left">
  <div>
    <span class="rounded-md shadow-sm">
      <button type="button" class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150" id="options-menu" aria-haspopup="true" aria-expanded="true">
        Options
        <svg class="-mr-1 ml-2 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
          <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
        </svg>
      </button>
    </span>
  </div>

  <!--
    Dropdown panel, show/hide based on dropdown state.

    Entering: "transition ease-out duration-100"
      From: "transform opacity-0 scale-95"
      To: "transform opacity-100 scale-100"
    Leaving: "transition ease-in duration-75"
      From: "transform opacity-100 scale-100"
      To: "transform opacity-0 scale-95"
  -->
  <div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
    <div class="rounded-md bg-white shadow-xs">
      <div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Account settings</a>
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Support</a>
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">License</a>
        <form method="POST" action="#">
          <button type="submit" class="block w-full text-left px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
            Sign out
          </button>
        </form>
      </div>
    </div>
  </div>
</div>
```

Now let's create a dropdown stimulus controller we can connect to our dropdown element. We will add two targets `button` and `menu` and the `toggleMenu` function.

```js
// dropdown_controller.js
import {Controller} from "stimulus"

export default class extends Controller {
    static targets = ["menu", "button"]

    toggleMenu() {
        if (this.menuTarget.classList.contains('hidden')) {
            // enter transition
        } else {
            // leave transition
        }
    }
}
```

Now we can connect the controller to the dropdown.

```html
<!-- add dropdown controller -->
<div data-controller="dropdown" class="relative inline-block text-left">
  <div>
    <span class="rounded-md shadow-sm">
      
      <!-- Add button target and click action -->
      <button type="button" data-target="dropdown.button" data-action="click->dropdown#toggleMenu" class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150" id="options-menu" aria-haspopup="true" aria-expanded="true">
        Options
        <svg class="-mr-1 ml-2 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
          <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
        </svg>
      </button>
    </span>
  </div>

  <!-- add menu target -->
  <div data-target="dropdown.menu" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
    <div class="rounded-md bg-white shadow-xs">
      <div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Account settings</a>
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Support</a>
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">License</a>
        <form method="POST" action="#">
          <button type="submit" class="block w-full text-left px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
            Sign out
          </button>
        </form>
      </div>
    </div>
  </div>
</div>
```

Now that the controller is connected let's implement the suggested transitions. 

```html
 <!--
    Dropdown panel, show/hide based on dropdown state.

    Entering: "transition ease-out duration-100"
      From: "transform opacity-0 scale-95"
      To: "transform opacity-100 scale-100"
    Leaving: "transition ease-in duration-75"
      From: "transform opacity-100 scale-100"
      To: "transform opacity-0 scale-95"
  -->
```

To do this, `el-transiton` expects `data-transition-*` attributes or an animation class. We will use data attributes which is more in line with how Apline.js works. Here is our updated html with the transitions declared.

```html
<div data-controller="dropdown" class="relative inline-block text-left">
  <div>
    <span class="rounded-md shadow-sm">
      <button type="button" data-target="dropdown.button" data-action="click->dropdown#toggleMenu" class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150" id="options-menu" aria-haspopup="true" aria-expanded="true">
        Options
        <svg class="-mr-1 ml-2 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
          <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
        </svg>
      </button>
    </span>
  </div>

<!-- add transition data attributes. -->
<div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg hidden"
       data-target="dropdown.menu"
       data-transition-enter="transition ease-out duration-100"
       data-transition-enter-start="transform opacity-0 scale-95"
       data-transition-enter-end="transform opacity-100 scale-100"
       data-transition-leave="transition ease-in duration-75"
       data-transition-leave-start="transform opacity-100 scale-100"
       data-transition-leave-end="transform opacity-0 scale-95"
  >
    <div class="rounded-md bg-white shadow-xs">
      <div class="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Account settings</a>
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">Support</a>
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">License</a>
        <form method="POST" action="#">
          <button type="submit" class="block w-full text-left px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
            Sign out
          </button>
        </form>
      </div>
    </div>
  </div>
</div>
```

Finally, we can update our stimulus dropdown controller to execute the enter/leave transition effects. 

```js
import {Controller} from "stimulus"
// import the enter leave functions
import {enter, leave} from 'el-transition'; 

export default class extends Controller {
    static targets = ["menu", "button"]

    // call the enter and leave functions
    toggleMenu() {
        if(this.menuTarget.classList.contains('hidden')) {
            enter(this.menuTarget)
        } else {
            leave(this.menuTarget)
        }
    }
}
```

That's it! 

You can find a demo here:
 {% glitch stimulus-tailwind-ui-enter-leave file=src/controllers/dropdown_controller.js %}

### Conclusion

I am stoked to be able to support TailwindUI's suggested transition effects as the team puts a lot of care into it and they look nice! While this example is specific to stimulus.js the `el-transition` is framework agnostic.