Skip to main content

Overview

Vuetify provides a set of reusable transition components for animating elements. These transitions follow Material Design motion principles and can be used with Vue’s <Transition> and <TransitionGroup> components.

Import

import {
  VFadeTransition,
  VScaleTransition,
  VSlideXTransition,
  VSlideYTransition,
  VExpandTransition,
  // ... and more
} from 'vuetify/components'

Available Transitions

Fade Transitions

VFadeTransition
Simple opacity fade in/out

Scale Transitions

VScaleTransition
Scale from/to center with fade
VFabTransition
Scale transition optimized for FAB buttons (from center, out-in mode)

Slide Transitions

VSlideXTransition
Slide horizontally from left
VSlideXReverseTransition
Slide horizontally from right
VSlideYTransition
Slide vertically from top
VSlideYReverseTransition
Slide vertically from bottom

Scroll Transitions

VScrollXTransition
Scroll effect horizontally from left
VScrollXReverseTransition
Scroll effect horizontally from right
VScrollYTransition
Scroll effect vertically from top
VScrollYReverseTransition
Scroll effect vertically from bottom

Dialog Transitions

VDialogTopTransition
Dialog slide from top
VDialogBottomTransition
Dialog slide from bottom
VDialogTransition
Default dialog transition

Expand Transitions

VExpandTransition
Vertical expand/collapse transition
VExpandXTransition
Horizontal expand/collapse transition

Shared Props

All transition components support these props:
disabled
boolean
default:"false"
Disable the transition
group
boolean
default:"false"
Use TransitionGroup instead of Transition (for lists)
hideOnLeave
boolean
default:"false"
Hide element with display: none during leave transition
leaveAbsolute
boolean
default:"false"
Position element absolutely during leave transition
mode
string
Transition mode (‘in-out’, ‘out-in’, or ‘default’)
origin
string
Transform origin for the transition

Usage Examples

Basic Fade

<script setup>
import { ref } from 'vue'

const show = ref(true)
</script>

<template>
  <div>
    <v-btn @click="show = !show">Toggle</v-btn>
    
    <v-fade-transition>
      <v-card v-if="show">
        Content fades in and out
      </v-card>
    </v-fade-transition>
  </div>
</template>

Scale Transition

<script setup>
import { ref } from 'vue'

const show = ref(true)
</script>

<template>
  <v-scale-transition>
    <v-icon v-if="show" size="64">
      mdi-check-circle
    </v-icon>
  </v-scale-transition>
</template>

Slide Transitions

<script setup>
import { ref } from 'vue'

const show = ref(true)
</script>

<template>
  <div>
    <v-slide-x-transition>
      <v-card v-if="show">Slides from left</v-card>
    </v-slide-x-transition>
    
    <v-slide-y-transition>
      <v-card v-if="show">Slides from top</v-card>
    </v-slide-y-transition>
  </div>
</template>

List with TransitionGroup

<script setup>
import { ref } from 'vue'

const items = ref(['Item 1', 'Item 2', 'Item 3'])

function removeItem(index: number) {
  items.value.splice(index, 1)
}
</script>

<template>
  <v-list>
    <v-slide-y-transition group>
      <v-list-item
        v-for="(item, i) in items"
        :key="item"
      >
        {{ item }}
        <template #append>
          <v-btn icon size="small" @click="removeItem(i)">
            <v-icon>mdi-close</v-icon>
          </v-btn>
        </template>
      </v-list-item>
    </v-slide-y-transition>
  </v-list>
</template>

Expand Transition

<script setup>
import { ref } from 'vue'

const expanded = ref(false)
</script>

<template>
  <v-card>
    <v-card-title @click="expanded = !expanded">
      Click to expand
      <v-icon>{{ expanded ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
    </v-card-title>
    
    <v-expand-transition>
      <div v-if="expanded">
        <v-divider />
        <v-card-text>
          This content expands and collapses smoothly
        </v-card-text>
      </div>
    </v-expand-transition>
  </v-card>
</template>

Custom Origin

<template>
  <v-scale-transition origin="top left">
    <v-card v-if="show">
      Scales from top left corner
    </v-card>
  </v-scale-transition>
</template>

Mode: Out-In

<script setup>
import { ref } from 'vue'

const current = ref('first')
</script>

<template>
  <div>
    <v-btn @click="current = current === 'first' ? 'second' : 'first'">
      Switch
    </v-btn>
    
    <v-fade-transition mode="out-in">
      <v-card v-if="current === 'first'" key="first">
        First card
      </v-card>
      <v-card v-else key="second">
        Second card
      </v-card>
    </v-fade-transition>
  </div>
</template>

Staggered List Animation

<script setup>
import { ref } from 'vue'

const items = ref(['Item 1', 'Item 2', 'Item 3', 'Item 4'])
</script>

<template>
  <v-list>
    <v-slide-x-transition 
      group
      hide-on-leave
    >
      <v-list-item
        v-for="(item, i) in items"
        :key="item"
        :style="{ transitionDelay: `${i * 50}ms` }"
      >
        {{ item }}
      </v-list-item>
    </v-slide-x-transition>
  </v-list>
</template>

FAB Button

<script setup>
import { ref } from 'vue'

const show = ref(true)
</script>

<template>
  <v-fab-transition>
    <v-btn
      v-if="show"
      icon
      color="primary"
      size="large"
    >
      <v-icon>mdi-plus</v-icon>
    </v-btn>
  </v-fab-transition>
</template>
<script setup>
import { ref } from 'vue'

const current = ref(0)
const images = ['/img1.jpg', '/img2.jpg', '/img3.jpg']
</script>

<template>
  <div>
    <v-slide-x-transition mode="out-in">
      <v-img :key="current" :src="images[current]" />
    </v-slide-x-transition>
    
    <v-btn @click="current = (current - 1 + images.length) % images.length">
      Previous
    </v-btn>
    <v-btn @click="current = (current + 1) % images.length">
      Next
    </v-btn>
  </div>
</template>

Conditional Messages

<script setup>
import { ref } from 'vue'

const message = ref<string | null>(null)

function showSuccess() {
  message.value = 'Success!'
  setTimeout(() => message.value = null, 3000)
}

function showError() {
  message.value = 'Error!'
  setTimeout(() => message.value = null, 3000)
}
</script>

<template>
  <div>
    <v-slide-y-transition>
      <v-alert v-if="message" :type="message.includes('Success') ? 'success' : 'error'">
        {{ message }}
      </v-alert>
    </v-slide-y-transition>
  </div>
</template>

Creating Custom Transitions

CSS Transition

import { createCssTransition } from 'vuetify/components/transitions'

export const VMyTransition = createCssTransition(
  'my-transition',
  'center center', // transform origin
  'out-in'        // mode
)

JavaScript Transition

import { createJavascriptTransition } from 'vuetify/components/transitions'

export const VMyJsTransition = createJavascriptTransition(
  'my-js-transition',
  {
    onBeforeEnter(el: HTMLElement) {
      el.style.opacity = '0'
    },
    onEnter(el: HTMLElement, done: () => void) {
      el.style.transition = 'opacity 0.3s'
      el.style.opacity = '1'
      setTimeout(done, 300)
    },
    onLeave(el: HTMLElement, done: () => void) {
      el.style.transition = 'opacity 0.3s'
      el.style.opacity = '0'
      setTimeout(done, 300)
    }
  }
)

Accessibility

  • Transitions respect prefers-reduced-motion media query
  • Set disabled prop to disable transitions for accessibility
  • Use semantic HTML within transitions
  • Ensure content is accessible during transition

Performance Tips

  1. Use CSS transforms (translate, scale) instead of position changes
  2. Avoid animating layout properties (width, height, margin)
  3. Use will-change CSS property sparingly
  4. Prefer v-show over v-if for frequent toggles
  5. Use hideOnLeave for better performance with complex content

Notes

  • All transitions use CSS for smooth 60fps animations
  • Expand transitions use JavaScript for dynamic height calculation
  • Group mode requires unique :key on each item
  • Transition duration is typically 150-300ms
  • Origin affects scale and rotation transformations

See Also

Build docs developers (and LLMs) love