Variants
An example of orchestrating animation sequences using Motion for Vue's variants.
Source code
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { motion,stagger,useDomRef, type MotionProps } from 'motion-v'
const isOpen = ref(false)
const containerRef = useDomRef()
const dimensions = ref({ width: 0, height: 0 })
onMounted(() => {
if (containerRef.value) {
dimensions.value.width = containerRef.value.offsetWidth
dimensions.value.height = containerRef.value.offsetHeight
}
})
const toggle = () => {
isOpen.value = !isOpen.value
}
const navVariants: MotionProps['variants'] = {
open: {
transition: { delayChildren: stagger(0.07, { startDelay: 0.2 }) },
},
closed: {
transition: { delayChildren: stagger(0.05, { from: "last" }) },
},
}
const itemVariants = {
open: {
y: 0,
opacity: 1,
transition: {
y: { stiffness: 1000, velocity: -100 }
}
},
closed: {
y: 50,
opacity: 0,
transition: {
y: { stiffness: 1000 }
}
}
}
const sidebarVariants: MotionProps['variants'] = {
open: (height: any = 1000) => ({
clipPath: `circle(${height * 2 + 200}px at 40px 40px)`,
transition: {
type: "spring",
stiffness: 20,
restDelta: 2
}
}),
closed: {
clipPath: "circle(30px at 40px 40px)",
transition: {
delay: 0.2,
type: "spring",
stiffness: 400,
damping: 40
}
}
}
const colors = ["#FF008C", "#D309E1", "#9C1AFF", "#7700FF", "#4400FF"]
</script>
<template>
<div>
<div class="container">
<motion.nav
:initial="false"
:animate="isOpen ? 'open' : 'closed'"
:custom="dimensions.height"
ref="containerRef"
class="nav"
>
<motion.div class="background" :variants="sidebarVariants" />
<!-- Navigation -->
<motion.ul class="list" :variants="navVariants">
<motion.li
v-for="i in 5"
:key="i-1"
class="list-item"
:variants="itemVariants"
:whilePress="{ scale: 0.95 }"
:whileHover="{ scale: 1.1 }"
>
<div class="icon-placeholder" :style="{ border: `2px solid ${colors[i-1]}` }" />
<div class="text-placeholder" :style="{ border: `2px solid ${colors[i-1]}` }" />
</motion.li>
</motion.ul>
<!-- Menu Toggle -->
<button class="toggle-container" @click="toggle">
<svg width="23" height="23" viewBox="0 0 23 23">
<motion.path
fill="transparent"
stroke-width="3"
stroke="hsl(0, 0%, 18%)"
stroke-linecap="round"
:variants="{
closed: { d: 'M 2 2.5 L 20 2.5' },
open: { d: 'M 3 16.5 L 17 2.5' }
}"
/>
<motion.path
fill="transparent"
stroke-width="3"
stroke="hsl(0, 0%, 18%)"
stroke-linecap="round"
d="M 2 9.423 L 20 9.423"
:variants="{
closed: { opacity: 1 },
open: { opacity: 0 }
}"
:transition="{ duration: 0.1 }"
/>
<motion.path
fill="transparent"
stroke-width="3"
stroke="hsl(0, 0%, 18%)"
stroke-linecap="round"
:variants="{
closed: { d: 'M 2 16.346 L 20 16.346' },
open: { d: 'M 3 2.5 L 17 16.346' }
}"
/>
</svg>
</button>
</motion.nav>
</div>
</div>
</template>
<style scoped>
.container {
position: relative;
display: flex;
justify-content: flex-start;
align-items: stretch;
flex: 1;
width: 500px;
max-width: 100%;
height: 400px;
background-color: var(--accent);
border-radius: 20px;
overflow: hidden;
}
.nav {
width: 300px;
}
.background {
background-color: #f5f5f5;
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 300px;
}
.toggle-container {
outline: none;
border: none;
-webkit-user-select: none;
-moz-user-select: none;
cursor: pointer;
position: absolute;
top: 18px;
left: 15px;
width: 50px;
height: 50px;
border-radius: 50%;
background: transparent;
}
.list {
list-style: none;
padding: 25px;
margin: 0;
position: absolute;
top: 80px;
width: 230px;
}
.list-item {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0;
margin: 0;
list-style: none;
margin-bottom: 20px;
cursor: pointer;
}
.icon-placeholder {
width: 40px;
height: 40px;
border-radius: 50%;
flex: 40px 0;
margin-right: 20px;
}
.text-placeholder {
border-radius: 5px;
width: 200px;
height: 20px;
flex: 1;
}
</style>Related examples
Latest in Vue
Motion+
Unlock all 400+ examples
- Source code for every Plus example.
- Provide examples direct to your agent via Motion's MCP.
- Lifetime access to new examples and APIs.








