Files
agent-skills/skills/vue-best-practices/reference/transition-nested-duration.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

183 lines
4.9 KiB
Markdown

---
title: Specify Explicit Duration for Nested Transitions
impact: MEDIUM
impactDescription: Nested transitions with different timings may end prematurely when Vue detects only the root element's transition end
type: gotcha
tags: [vue3, transition, animation, duration, nested, timing]
---
# Specify Explicit Duration for Nested Transitions
**Impact: MEDIUM** - When transitioning elements that contain nested child elements with different animation timings, Vue by default listens only for the first `transitionend` or `animationend` event on the **root** transition element. This means if inner elements have longer or delayed animations, they may be cut off when the root element's transition completes.
## Task Checklist
- [ ] Identify if your transition contains nested elements with different animation durations
- [ ] Use the `:duration` prop to specify the total time Vue should wait
- [ ] Consider using separate enter and leave durations if they differ
- [ ] Test animations to ensure nested elements complete fully
**Problematic Code:**
```vue
<template>
<!-- BAD: Inner element has longer animation that gets cut off -->
<Transition name="nested">
<div v-if="show" class="outer">
<div class="inner">Hello</div>
</div>
</Transition>
</template>
<style>
.nested-enter-active .outer,
.nested-leave-active .outer {
transition: opacity 0.3s ease;
}
.nested-enter-active .inner,
.nested-leave-active .inner {
/* This 0.5s animation gets cut off at 0.3s when outer finishes! */
transition: transform 0.5s ease 0.2s; /* 0.2s delay + 0.5s = 0.7s total */
}
.nested-enter-from .outer,
.nested-leave-to .outer {
opacity: 0;
}
.nested-enter-from .inner,
.nested-leave-to .inner {
transform: translateX(-30px);
}
</style>
```
**Correct Code:**
```vue
<template>
<!-- GOOD: Explicit duration ensures all nested animations complete -->
<Transition name="nested" :duration="700">
<div v-if="show" class="outer">
<div class="inner">Hello</div>
</div>
</Transition>
</template>
<style>
.nested-enter-active .outer,
.nested-leave-active .outer {
transition: opacity 0.3s ease;
}
.nested-enter-active .inner,
.nested-leave-active .inner {
/* Now this animation completes fully */
transition: transform 0.5s ease 0.2s;
}
.nested-enter-from .outer,
.nested-leave-to .outer {
opacity: 0;
}
.nested-enter-from .inner,
.nested-leave-to .inner {
transform: translateX(-30px);
}
</style>
```
## Different Enter and Leave Durations
```vue
<template>
<!-- GOOD: Separate durations for enter and leave -->
<Transition
name="complex"
:duration="{ enter: 500, leave: 800 }"
>
<div v-if="show" class="container">
<h1 class="title">Title</h1>
<p class="content">Content with staggered animation</p>
</div>
</Transition>
</template>
<style>
/* Enter: title first, then content */
.complex-enter-active .title {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.complex-enter-active .content {
transition: opacity 0.3s ease 0.2s, transform 0.3s ease 0.2s;
}
/* Leave: content first, then title (reverse order) */
.complex-leave-active .content {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.complex-leave-active .title {
transition: opacity 0.5s ease 0.3s, transform 0.5s ease 0.3s;
}
.complex-enter-from .title,
.complex-enter-from .content,
.complex-leave-to .title,
.complex-leave-to .content {
opacity: 0;
transform: translateY(20px);
}
</style>
```
## Choreographed Staggered Animations
```vue
<template>
<Transition name="stagger" :duration="800">
<div v-if="show" class="card">
<img class="card-image" src="..." />
<h2 class="card-title">Title</h2>
<p class="card-body">Body text...</p>
<button class="card-action">Action</button>
</div>
</Transition>
</template>
<style>
/* Staggered entrance: image -> title -> body -> action */
.stagger-enter-active .card-image { transition: all 0.3s ease; }
.stagger-enter-active .card-title { transition: all 0.3s ease 0.1s; }
.stagger-enter-active .card-body { transition: all 0.3s ease 0.2s; }
.stagger-enter-active .card-action { transition: all 0.3s ease 0.3s; }
/* Total: 0.3s delay + 0.3s animation = 0.6s, but use 800ms for safety */
.stagger-enter-from .card-image,
.stagger-enter-from .card-title,
.stagger-enter-from .card-body,
.stagger-enter-from .card-action {
opacity: 0;
transform: translateY(10px);
}
</style>
```
## Calculating Duration
Use this formula to calculate the correct duration:
```
duration = max(delay + animation_duration) for all nested elements
```
Example:
- Element A: no delay, 300ms duration = 300ms total
- Element B: 100ms delay, 300ms duration = 400ms total
- Element C: 200ms delay, 500ms duration = 700ms total
**Required `:duration`**: 700 (or slightly higher for safety margin)
## Reference
- [Vue.js Transition - Nested Transitions](https://vuejs.org/guide/built-ins/transition.html#nested-transitions-and-explicit-transition-durations)