Files
stack/packages/mosaic/framework/skills/vue/references/advanced-patterns.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

6.1 KiB

name, description
name description
advanced-patterns Vue 3 built-in components (Transition, Teleport, Suspense, KeepAlive) and advanced directives

Built-in Components & Directives

Transition

Animate enter/leave of a single element or component.

<template>
  <Transition name="fade">
    <div v-if="show">Content</div>
  </Transition>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

CSS Classes

Class When
{name}-enter-from Start state for enter
{name}-enter-active Active state for enter (add transitions here)
{name}-enter-to End state for enter
{name}-leave-from Start state for leave
{name}-leave-active Active state for leave
{name}-leave-to End state for leave

Transition Modes

<!-- Wait for leave to complete before enter -->
<Transition name="fade" mode="out-in">
  <component :is="currentView" />
</Transition>

JavaScript Hooks

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @leave="onLeave"
  :css="false"
>
  <div v-if="show">Content</div>
</Transition>

<script setup lang="ts">
function onEnter(el: Element, done: () => void) {
  // Animate with JS library
  gsap.to(el, { opacity: 1, onComplete: done });
}
</script>

Appear on Initial Render

<Transition appear name="fade">
  <div>Shows with animation on mount</div>
</Transition>

TransitionGroup

Animate list items. Each child must have a unique key.

<template>
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.text }}
    </li>
  </TransitionGroup>
</template>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
/* Move animation for reordering */
.list-move {
  transition: transform 0.3s ease;
}
</style>

Teleport

Render content to a different DOM location.

<template>
  <button @click="open = true">Open Modal</button>

  <Teleport to="body">
    <div v-if="open" class="modal">Modal content rendered at body</div>
  </Teleport>
</template>

Props

<!-- CSS selector -->
<Teleport to="#modal-container">

<!-- DOM element -->
<Teleport :to="targetElement">

<!-- Disable teleport conditionally -->
<Teleport to="body" :disabled="isMobile">

<!-- Defer until target exists (Vue 3.5+) -->
<Teleport defer to="#late-rendered-target">

Suspense

Handle async dependencies with loading states. Experimental feature.

<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

Async Dependencies

Suspense waits for:

  • Components with async setup()
  • Components using top-level await in <script setup>
  • Async components created with defineAsyncComponent
<!-- AsyncComponent.vue -->
<script setup lang="ts">
const data = await fetch('/api/data').then((r) => r.json());
</script>

Events

<Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback">
  ...
</Suspense>

KeepAlive

Cache component instances when toggled.

<template>
  <KeepAlive>
    <component :is="currentTab" />
  </KeepAlive>
</template>

Include/Exclude

<!-- By name (string or regex) -->
<KeepAlive include="ComponentA,ComponentB">
<KeepAlive :include="/^Tab/">
<KeepAlive :include="['TabA', 'TabB']">

<!-- Exclude -->
<KeepAlive exclude="ModalComponent">

<!-- Max cached instances -->
<KeepAlive :max="10">

Lifecycle Hooks

import { onActivated, onDeactivated } from 'vue';

onActivated(() => {
  // Called when component is inserted from cache
  fetchLatestData();
});

onDeactivated(() => {
  // Called when component is removed to cache
  pauseTimers();
});

v-memo

Skip re-renders when dependencies unchanged. Use for performance optimization.

<template>
  <div v-for="item in list" :key="item.id" v-memo="[item.selected]">
    <!-- Only re-renders when item.selected changes -->
    <ExpensiveComponent :item="item" />
  </div>
</template>

Equivalent to v-once when empty:

<div v-memo="[]">Never updates</div>

v-once

Render once, skip all future updates.

<span v-once>Static: {{ neverChanges }}</span>

Custom Directives

Create reusable DOM manipulations.

// Directive definition
const vFocus: Directive<HTMLElement> = {
  mounted: (el) => el.focus(),
};

// Full hooks
const vColor: Directive<HTMLElement, string> = {
  created(el, binding, vnode, prevVnode) {},
  beforeMount(el, binding) {},
  mounted(el, binding) {
    el.style.color = binding.value;
  },
  beforeUpdate(el, binding) {},
  updated(el, binding) {
    el.style.color = binding.value;
  },
  beforeUnmount(el, binding) {},
  unmounted(el, binding) {},
};

Directive Arguments & Modifiers

<div v-color:background.bold="'red'">

<script setup lang="ts">
const vColor: Directive<HTMLElement, string> = {
  mounted(el, binding) {
    // binding.arg = 'background'
    // binding.modifiers = { bold: true }
    // binding.value = 'red'
    el.style[binding.arg || 'color'] = binding.value
    if (binding.modifiers.bold) {
      el.style.fontWeight = 'bold'
    }
  }
}
</script>

Global Registration

// main.ts
app.directive('focus', {
  mounted: (el) => el.focus(),
});