Skip to content

Instantly share code, notes, and snippets.

@dillingham
Last active July 6, 2023 16:20
Show Gist options
  • Star 43 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save dillingham/cbfbbde98699029cc087a09e759903c0 to your computer and use it in GitHub Desktop.
Save dillingham/cbfbbde98699029cc087a09e759903c0 to your computer and use it in GitHub Desktop.

Converting Tailwind UI Alpine transitions to Vue transitions

After you copy a component from the Tailwind UI library and begin to adapt it from Vue JS to Alpine JS .. you may wonder what to do about the transitions. As I'm exploring this myself, I am documenting it for others in the same boat.

Things to be aware of:

  • Alpine calls the beginning and ending states "start" & "end"
  • Vue calls the beginning and ending states "from" and "to"
  • Alpine has inline "directives" ie x-transition:enter="classes"
  • Vue has a wrapper component that applies classes to the child
  • Alpine applies the classes you pass it for each state, :enter-start="class"
  • Vue applies default classes to the child element, class="enter-start"
  • Vue's component has props for changing default classes
  • Vue does not have native click away support 

So with all that in mind, I think a simple way to quickly transition from Alpine JS to Vue JS transitions would be the following steps.

1: Change the inline syntax to stacked

Multi select in your IDE and put each x-transition on a new line

<div
    x-transition:enter="transition ease-out duration-200"
    x-transition:enter-start="transform opacity-0 scale-95">

2: Change "-start" to "-from" and "-end" to "-to"

As previously mentioned, Vue uses "from" and "to" not "start" and "from"

3: Remove x-transition: from each line

We are converting these from Alpine directives to Vue static properties

<div
    v-show="open"
    enter="transition ease-out duration-200"
    enter-from="transform opacity-0 scale-95">

4: Move the props to a <transition> wrapper component

<transition
    enter="transition ease-out duration-200"
    enter-from="transform opacity-0 scale-95">
        <div v-show="open">...</div>
</transition>

5: Add "-class" to the end of each prop

Vue's <transition> component allows you to add the classes to apply to the child component at each point in the transition lifecycle. The property names perfectly line up and just require us to add '-class' to the end of each.

<transition
    enter-class="transition ease-out duration-200"
    enter-from-class="transform opacity-0 scale-95">
        <div v-show="open">...</div>
</transition>

6: Move transform to child element

The transform needs to be added to the child element and can be removed from the transition properties

<transition
    enter-class="ease-out duration-200"
    enter-from-class="opacity-0 scale-95">
        <div v-show="open" class="transform">...</div>
</transition>

Click away handling

Alpine comes with a handy click.away handling. Vue does not.

You can register an event listener in mounted to handle this behavior like so:

1: Add a ref="component_name" to the parent element within component

Tell your component what html element it should watch clicks outside of

2: Listen for clicks and check if the element is outside of the ref element.

mounted() {
    document.addEventListener('click', event => {    
        event.stopPropagation();
        if(!this.$refs.component_name.contains(event.target)) {
            this.open = false;
        }
    });
}
@nathan-isaac
Copy link

Hi @dillingham. I wasn't able to get your mappings to work. After some digging this is what worked for me.

x-transition:enter="..." -> enter-active-class="..."
x-transition:enter-start="..." -> enter-class="..."
x-transition:enter-end="..." -> enter-to-class="..."
x-transition:leave="..." -> leave-active-class="..."
x-transition:leave-start="..." -> leave-class="..."
x-transition:leave-end="..." -> leave-to-class="..."

References:

@timmaier
Copy link

timmaier commented Mar 12, 2020

Hey thanks for this reference guide!

Some of the components that have nested x-data tags use the same variable data names. Because they are nested this works with Alpine.js. However when converting the component to Vue.js one has to change the name of the nested one to be unique. Then it's possible to declare and have all animations and click handlers working as intended.

Also I had to remove some hidden class attributes to get navigation to show on mobile because the class was set to 'hidden' at all times.
If you remove the class="hidden", and leave the vue set classes i.e. :class="{'hidden': !open}"

@nathandaly
Copy link

Maybe this will help someone :)

<template>
  <transition
    :enter-active-class="enter"
    :enter-class="enterStart"
    :enter-to-class="enterEnd"
    :leave-active-class="leave"
    :leave-class="leaveStart"
    :leave-to-class="leaveEnd"
  >
    <slot v-if="show"></slot>
  </transition>
</template>

<script lang="ts">
import Vue from "vue";

/**
 * Translating Alpine.js transitions to vue.
 * @see:https://gist.github.com/dillingham/cbfbbde98699029cc087a09e759903c0#gistcomment-3204969
 */
export default Vue.extend({
  props: {
    show: { type: Boolean, default: false },
    enter: { type: String, default: "transition ease-out duration-200" },
    enterStart: { type: String, default: "transform opacity-0 scale-95" },
    enterEnd: { type: String, default: "transform opacity-100 scale-100" },

    leave: { type: String, default: "transition ease-in duration-75" },
    leaveStart: { type: String, default: "transform opacity-100 scale-100" },
    leaveEnd: { type: String, default: "transform opacity-0 scale-95" }
  }
});
</script>

@elkhayder
Copy link

Hi @dillingham. I wasn't able to get your mappings to work. After some digging this is what worked for me.

x-transition:enter="..." -> enter-active-class="..."
x-transition:enter-start="..." -> enter-class="..."
x-transition:enter-end="..." -> enter-to-class="..."
x-transition:leave="..." -> leave-active-class="..."
x-transition:leave-start="..." -> leave-class="..."
x-transition:leave-end="..." -> leave-to-class="..."

References:

Thank you for this summary, it worked for me like a charm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment