Bug: <ViewTransition> nested inside a portal-mounted parent never receives its own view-transition-name/class when both mount in the same commit
Per the ViewTransitionProps.enter/exit type documentation:
▎ Combined with className if this or its parent Component is mounted and there's no other with the same name being deleted.
This implies that a nested should still receive its own enter/exit view-transition-class even when it mounts as part of an ancestor 's own mount (i.e. both appear in the same commit).
In practice, when a is nested inside another that is rendered through ReactDOM.createPortal, and both mount together via a single startTransition-wrapped state update, the inner never gets a view-transition-name/view-transition-class assigned to its DOM node at all — it stays "none" for the entire transition, and no CSS animation targeting ::view-transition-old()/::view-transition-new() for that class ever runs. Only the outermost gets a real view-transition-name (e.g. t_0) and its class.
This was verified directly by polling getComputedStyle(node).viewTransitionName / .viewTransitionClass on the inner node throughout the transition (stays "none" the whole time) and by inspecting document.getAnimations({ subtree: true }), which never lists any animation for the inner class, regardless of whether the inner is given an explicit name prop.
React version:
- react: 19.3.0
- react-dom: 19.3.0
- Browser: Chromium 153.0.8010.12 (also reproduces in current stable Chrome/Edge)
- OS: macOS 26.6.2
Steps To Reproduce
- Sandbox environment will help you out to understand how this bug occurred.
Link to code example: https://codesandbox.io/p/sandbox/magical-colden-ldn5fl
The current behavior
Only the outer (backdrop) is activated: it gets a real view-transition-name (e.g. t_0) and its enter/exit class is applied, so its fade animation runs. The inner, nested (box) never gets a view-transition-name or view-transition-class assigned — both remain "none" for the entire lifetime of the transition — so its enter/exit class and animation never apply, even though it mounts in the exact same commit as its ancestor, matching the condition described in the prop docs ("...or its parent Component is mounted"). This happens with or without an explicit name prop on the inner .
The expected behavior
Per the documented behavior of enter/exit ("Combined with className if this or its parent Component is mounted..."), the inner should receive its own view-transition-name (auto-generated or explicit) and have its enter class applied to its DOM node when it mounts together with its parent, so that nested boundaries can each have independent CSS-driven enter/exit animations (e.g. backdrop fades while the box independently scales/pops).
Source: facebook/react