DEV Community

Robert Wagner
Robert Wagner

Posted on • Originally published at shipshape.io

1

Tailwind UI Dropdowns with Ember

Tailwind CSS has exploded in popularity recently, and so has their paid set of UI components, Tailwind UI.
They have a lot of great components, most of which you can copy and paste into your project and they will "just work", but whenever an example requires some JavaScript, things get a little more complex.

For example, one of the Tailwind UI dropdown component examples is:

<!-- This example requires Tailwind CSS v2.0+ -->
<div class="relative inline-block text-left">
  <div>
    <button
      class="bg-gray-100 rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
      id="options-menu"
      aria-haspopup="true"
      aria-expanded="true"
    >
      <span class="sr-only">Open options</span>
      <!-- Heroicon name: dots-vertical -->
      <svg
        class="h-5 w-5"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 20 20"
        fill="currentColor"
        aria-hidden="true"
      >
        <path
          d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
        />
      </svg>
    </button>
  </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 bg-white ring-1 ring-black ring-opacity-5"
  >
    <div
      class="py-1"
      role="menu"
      aria-orientation="vertical"
      aria-labelledby="options-menu"
    >
      <a
        href="#"
        class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
        role="menuitem"
        >Account settings</a
      >
      <a
        href="#"
        class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
        role="menuitem"
        >Support</a
      >
      <a
        href="#"
        class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover: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 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>
Enter fullscreen mode Exit fullscreen mode

Notice this part in the middle about the enter/leave transitions:

<!--
    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"
  -->
Enter fullscreen mode Exit fullscreen mode

So not only do we need to support transitions on enter/leave, but each transition needs to support a from and to state.

I did some digging and found a framework agnostic solution
el-transition, and was about to use it, but then I discovered an Ember specific solution, which fit my needs better, since my app was using Ember.

The ember-css-transitions addon seemed to fit my use case perfectly. It ships a css-transition modifier that supports all the enter/leave and to/from states we needed.

The end result in Ember was something like:

<div class="relative inline-block text-left">
  <button
    class="px-1 rounded transition-colors {{
      if (and this.isShown @showBackground) "bg-main"
    }}"
    type="button"
    {{on "click" (stop-propagation (set this "isShown" (not this.isShown)))}}
    {{on-click-outside
      (set this "isShown" false)
      eventType="mousedown"
      exceptSelector=".options-menu *"
    }}
  >
    {{yield to="trigger"}}
  </button>

  {{#if this.isShown}}
    <div
      class="options-menu absolute bg-menu mt-2 p-2 origin-top-{{
        this.position
      }} {{
        this.position
      }}-0 ring-1 ring-main rounded-md shadow-lg text-menu-text w-44 z-50"
      {{css-transition
        enterClass="transform opacity-0 scale-95"
        enterActiveClass="transition ease-out duration-100"
        enterToClass="transform opacity-100 scale-100"
        leaveClass="transform opacity-100 scale-100"
        leaveActiveClass="transition ease-in duration-75"
        leaveToClass="transform opacity-0 scale-95"
      }}
      {{on "click" (stop-propagation (set this "isShown" false))}}
    >
      {{yield to="content"}}
    </div>
  {{/if}}
</div>
Enter fullscreen mode Exit fullscreen mode

This applies all the transitions correctly, and gets us up and running with a Tailwind UI dropdown! 🎉 You may also have noticed the on-click-outside modifier and the Ember Named Blocks we are using here. More posts to follow going into more detail on those, but in the meantime hopefully this helps someone with transitions!

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

Top comments (0)

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay