JSX that imports the adapter from @vureact/runtime-core. Conditional rendering directives like v-if are converted to ternary expressions or logical AND operators, preserving the single-child constraint required by the transition system.
Step 2: Runtime Adapter Execution
The compiled <Transition> component acts as a lifecycle coordinator. It monitors the presence of its direct child, injects the appropriate CSS classes at each phase, and triggers JavaScript hooks in the correct sequence. The adapter uses Reactâs useLayoutEffect and requestAnimationFrame to synchronize with the browserâs paint cycle, ensuring that class injection occurs before the browser calculates layout.
Step 3: CSS Architecture Mapping
Vueâs transition system relies on a predictable class naming convention. The compiler maps the name attribute to a prefix, automatically generating six lifecycle classes. Custom class attributes override specific phases, enabling seamless integration with third-party animation libraries like Animate.css or custom design tokens.
Step 4: JavaScript Hook Synchronization
Event handlers prefixed with @ in Vue are transformed into camelCase props in React. The runtime adapter binds these handlers to the corresponding animation phases, passing the DOM element reference and optional done callbacks. This enables programmatic control over complex choreographies while maintaining CSS-driven performance for standard transitions.
New Code Examples
Example 1: Tab Switcher with Sequential Mode
import { Transition } from '@vureact/runtime-core';
import { useState } from 'react';
import './tab-animations.css';
export function TabSwitcher() {
const [activeTab, setActiveTab] = useState<'dashboard' | 'settings'>('dashboard');
return (
<div className="tab-container">
<div className="tab-nav">
<button onClick={() => setActiveTab('dashboard')}>Dashboard</button>
<button onClick={() => setActiveTab('settings')}>Settings</button>
</div>
<Transition name="panel-slide" mode="out-in">
{activeTab === 'dashboard' ? (
<div key="dashboard" className="tab-content">
<h2>Analytics Overview</h2>
<p>Real-time metrics and performance charts.</p>
</div>
) : (
<div key="settings" className="tab-content">
<h2>Configuration</h2>
<p>Manage preferences and integrations.</p>
</div>
)}
</Transition>
</div>
);
}
Example 2: Notification Toast with Custom Classes and Duration
import { Transition } from '@vureact/runtime-core';
import { useState, useCallback } from 'react';
export function NotificationSystem() {
const [isVisible, setIsVisible] = useState(false);
const [message, setMessage] = useState('');
const triggerNotification = useCallback((text: string) => {
setMessage(text);
setIsVisible(true);
setTimeout(() => setIsVisible(false), 3000);
}, []);
const handleEnter = useCallback((el: HTMLElement) => {
el.style.transform = 'translateY(0)';
}, []);
const handleLeave = useCallback((el: HTMLElement, done: () => void) => {
el.style.transform = 'translateY(-20px)';
el.style.opacity = '0';
setTimeout(done, 400);
}, []);
return (
<div>
<button onClick={() => triggerNotification('Data saved successfully')}>
Save Changes
</button>
<Transition
name="toast"
duration={{ enter: 350, leave: 400 }}
enterActiveClass="toast-enter-active"
leaveActiveClass="toast-leave-active"
onEnter={handleEnter}
onLeave={handleLeave}
>
{isVisible && (
<div key="notification" className="toast-message">
{message}
</div>
)}
</Transition>
</div>
);
}
Architecture Decisions and Rationale
The compiler deliberately avoids generating JavaScript-driven animation logic. Instead, it delegates motion to CSS transitions and transforms, reserving JavaScript hooks only for complex choreographies or DOM measurements. This decision aligns with browser rendering best practices: CSS animations run on the compositor thread, avoiding layout thrashing and maintaining 60fps performance even on low-end devices.
The adapter component uses a controlled rendering strategy. It maintains an internal state that tracks the presence of its child, delaying unmounting until the leave animation completes. This prevents Reactâs default reconciliation from immediately removing the DOM node, which would instantly cut off the animation. The mode prop is implemented through a queue system that sequences enter and leave phases, ensuring that overlapping animations do not cause visual glitches or z-index conflicts.
Pitfall Guide
1. The Keyless Switch Trap
Explanation: React relies on the key prop to identify elements during reconciliation. When switching between different components inside a <Transition>, omitting explicit keys causes React to reuse the same DOM node, preventing the leave animation from triggering.
Fix: Always assign stable, unique keys to conditional children. If keys are omitted, VuReact auto-generates random identifiers, but this behavior is unpredictable across re-renders. Explicit keys guarantee consistent animation triggering.
2. CSS Specificity Collisions
Explanation: Transition classes like .fade-enter-active are globally scoped by default. If multiple components use the same name attribute, styles will leak and override each other, causing inconsistent animation behavior.
Fix: Prefix transition names with component identifiers (e.g., modal-fade, sidebar-slide). Use CSS modules, scoped styles, or CSS-in-JS solutions to isolate transition classes. Avoid generic names like fade or slide in shared codebases.
3. Async State Desynchronization
Explanation: When state updates are batched or delayed (e.g., API responses, debounced inputs), the transition component may receive conflicting presence signals. This causes the adapter to skip phases or trigger animations out of sequence.
Fix: Synchronize state updates with the transition lifecycle. Use the duration prop to explicitly define animation timing, or wrap state changes in requestAnimationFrame to align with the browserâs paint cycle. Avoid rapid toggling of visibility flags.
4. Multi-Child Violation
Explanation: Vueâs <Transition> strictly enforces a single direct child. Wrapping multiple elements without a container causes the compiler to generate invalid JSX, and the runtime adapter will fail to track lifecycle phases correctly.
Fix: Wrap multiple elements in a single container div or fragment. If conditional rendering involves multiple nodes, consolidate them into a single wrapper that receives the transition classes.
5. JS Hook Overengineering
Explanation: Developers often use JavaScript hooks for simple opacity or transform animations, introducing unnecessary complexity and performance overhead. JS-driven animations run on the main thread and can cause frame drops during heavy reconciliation.
Fix: Reserve JavaScript hooks for DOM measurements, dynamic calculations, or complex choreographies. Use CSS classes for standard fades, slides, and scales. The compilerâs automatic class injection is optimized for compositor-thread execution.
6. Ignoring Sequential Mode Requirements
Explanation: When switching between UI states that occupy the same layout space, simultaneous enter and leave animations cause overlapping content, z-index conflicts, and layout shifts.
Fix: Explicitly set mode="out-in" or mode="in-out" based on the desired UX flow. out-in ensures the old content fully exits before the new content enters, preventing visual clutter. Use in-out when the new content should appear first, then push the old content out.
7. Cross-Framework Animation Mixing
Explanation: Combining VuReactâs transition adapter with third-party React animation libraries in the same component tree creates conflicting lifecycle management. Both systems attempt to control DOM presence and class injection, resulting in broken animations or memory leaks.
Fix: Standardize on a single animation paradigm per component tree. If migrating incrementally, isolate Vue-style transitions to migrated components and reserve React-native libraries for new features. Avoid nesting <Transition> components from different libraries.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Simple fade/slide transitions | CSS classes with name attribute | Compositor-thread execution, zero JS overhead | Low (reuses existing CSS) |
| Sequential UI switching | mode="out-in" or mode="in-out" | Prevents layout overlap and z-index conflicts | Low (single prop addition) |
| Third-party library integration | Custom class attributes (enterActiveClass, etc.) | Maps external animation tokens to lifecycle phases | Medium (requires CSS mapping) |
| Complex choreography | JavaScript hooks (onEnter, onLeave) | Enables DOM measurements and dynamic calculations | High (main thread overhead) |
| Rapid state toggling | Explicit duration prop | Synchronizes React reconciliation with animation timing | Low (predictable timing) |
Configuration Template
// vureact-transition.config.ts
import { defineConfig } from '@vureact/compiler';
export default defineConfig({
transition: {
// Enable automatic class generation
autoClassPrefix: true,
// Fallback duration for unconfigured transitions
defaultDuration: 300,
// Strict single-child validation
enforceSingleChild: true,
// Compositor optimization
enableWillChange: true,
// Hook execution context
hookContext: 'component', // or 'global'
},
css: {
// Scope transition classes to component boundaries
scopeMode: 'module',
// Generate CSS variables for duration tokens
generateTokens: true,
},
});
/* transition-tokens.css */
:root {
--transition-duration-fast: 200ms;
--transition-duration-normal: 300ms;
--transition-duration-slow: 500ms;
--transition-easing-standard: cubic-bezier(0.4, 0, 0.2, 1);
--transition-easing-decelerate: cubic-bezier(0.0, 0, 0.2, 1);
}
/* Base transition structure */
.[name]-enter-from,
.[name]-leave-to {
opacity: 0;
transform: scale(0.95);
}
.[name]-enter-active,
.[name]-leave-active {
transition: all var(--transition-duration-normal) var(--transition-easing-standard);
will-change: opacity, transform;
}
.[name]-enter-to,
.[name]-leave-from {
opacity: 1;
transform: scale(1);
}
Quick Start Guide
- Initialize the compiler: Run
npm install @vureact/compiler @vureact/runtime-core and configure vite.config.ts or webpack.config.js to process .vue files through the VuReact pipeline.
- Import the adapter: Add
import { Transition } from '@vureact/runtime-core'; to any React component that requires transition support.
- Wrap conditional content: Replace Vueâs
<Transition> with the imported adapter, converting v-if to ternary expressions and assigning explicit key props to children.
- Define CSS phases: Create a stylesheet with
*-enter-from, *-enter-active, *-enter-to, *-leave-from, *-leave-active, and *-leave-to classes using your chosen name prefix.
- Validate lifecycle: Run the application, toggle visibility states, and verify that enter and leave animations trigger correctly. Use browser DevTools to confirm CSS transitions execute without main thread interference.