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:

5 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
6 Likes

For what it’s worth, this was fixed here:

I haven’t personally had a chance to confirm the patch works.

1 Like