Achieving tailwind enter/leave transitions with LiveView.JS

Tailwind has nice transitions for components like slid-overs, dropdowns, and modals. For example, these are the transitions classes for a slideover:

Slide-over panel, show/hide based on slide-over state.

        Entering: "transform transition ease-in-out duration-500 sm:duration-700"
          From: "translate-x-full"
          To: "translate-x-0"
        Leaving: "transform transition ease-in-out duration-500 sm:duration-700"
          From: "translate-x-0"
          To: "translate-x-full"

These are easy to get right with Alpine.js using x-transitions, but is there a way to do them using LiveView.JS? Has anybody tried?

3 Likes

You would find an example here - live_beats/live_helpers.ex at master Ā· fly-apps/live_beats (github.com)

You can find a practical demonstration at Client-Side Tabs in LiveView with JS Commands Ā· Fly

6 Likes

As a supplement. GitHub - petalframework/petal_components: Phoenix Live View Components has a modal component which supports transition. Check it out.

3 Likes

Or the docs

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.JS.html#add_class/1

5 Likes

For what itā€™s worth, Iā€™ve tried to transcribe multiple component from TailwindUI and had the same experience as @subbu.

With Alpine.js, everything just works. Iā€™ve never been able to get Phoenix.LiveView.JS.transition (or the transition options of the other JS functions) to work right out of the box with these Tailwind transitions.

For example, with @subbuā€™s original post, Iā€™d put

JS.transition({"transform transition ease-in-out duration-500 sm:duration-700", "translate-x-full", "translate-x-0"})

But it doesnā€™t work right. (Again, despite working right in Alpine.)

Does anyone know whatā€™s going on here? Or how to get the desired behavior?

Can you show the full code of where you call the transition?

@tcoopman Thanks for chiming in!

I was mostly having trouble with entrance transitions. (Elements not previously shown.) I had to add these to get Tailwind transitions to apply correctly:

  1. style="display: none"
  2. class="start-classes"
  3. phx-mounted={JS.show(transition: {"transition-classes", "start-classes", "end-classes"}, time: ...)}

So for example, if a TailwindUI component has:

<!--
  Entering: "ease-out duration-300"
    From: "opacity-0"
    To: "opacity-100"
-->
<div>
  ...
</div>

I had to do:

<div
  style="display: none"
  class="opacity-0"
  phx-mounted={JS.show(
    transition: {"ease-out duration-300", "opacity-0", "opacity-100"},
    time: 300
  )}
>
  ...
</div>

Without this precise configuration, I wasnā€™t able to get the transition to display correctly. (Notice Iā€™m using JS.show instead of JS.transition.)

Hopefully someone out there finds this helpful! :slight_smile:


Itā€™s worth noting that Alpine.js makes this much less confusing to get right. You just take the 3 lists of classes from the TailwindUI example HTML and drop them into 3 corresponding attributes.

<div
  x-show="open"
  x-transition:enter="ease-out duration-300"
  x-transition:enter-start="opacity-0"
  x-transition:enter-end="opacity-100"
>
  ...
</div>

Perhaps LiveView could benefit to evolve more in that direction. :slight_smile:

4 Likes

I should note, the same issue applies with exit transitions.

With this TailwindUI specification:

<!--
  Leaving: "ease-in duration-200"
    From: "opacity-100"
    To: "opacity-0"
-->

You can do this in Alpine.js:

<div
  x-show="open"
  x-transition:leave="ease-in duration-200"
  x-transition:leave-start="opacity-100"
  x-transition:leave-end="opacity-0"
>
  ...
</div>

But this does not work in LiveView:

<div phx-remove={JS.transition({"ease-in duration-200", "opacity-100", "opacity-0"})}>
  ...
</div>

Instead, you have to do this:

<div phx-remove={JS.hide({"ease-in duration-200", "opacity-100", "opacity-0"})
  ...
</div>

(Again, use JS.hide instead of JS.transition.)

I hope this is a helpful reference to anyone else in this situation!

3 Likes

Iā€™ve confirmed that JS.add_class, JS.remove_class, and JS.transition apply the end classes at the wrong time.

Iā€™ve logged a bug report for LiveView here:

5 Likes

I think the answer here is that you can apply multiple classes in the 3-tuple separated by spaces. I guessed at this by looking at the linked source code, and then saw that it uses a function transition_class_names which calls class_names which uses String.split(" "") (source)

For example something specified as:

  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"

can be implemented as:

  def show_user_menu(js \\ %JS{}) do
    js
    |> JS.toggle(
      to: "#user_menu",
      in:
        {"transition ease-out duration-100", "transform opacity-0 scale-95",
         "transform opacity-100 scale-100"},
      out:
        {"transition ease-in duration-75", "transform opacity-100 scale-100",
         "transform opacity-0 scale-95"}
    )
  end
5 Likes

For what itā€™s worth, this was fixed here:

I havenā€™t personally had a chance to confirm the patch works.