diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs
new file mode 100644
index 0000000000..cb0d229110
--- /dev/null
+++ b/src/Avalonia.Controls/TransitioningContentControl.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Threading;
+using Avalonia.Animation;
+using Avalonia.Controls.Templates;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls;
+
+///
+/// Displays according to a .
+/// Uses to move between the old and new content values.
+///
+public class TransitioningContentControl : ContentControl
+{
+ private CancellationTokenSource? _lastTransitionCts;
+ private object? _currentContent;
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PageTransitionProperty =
+ AvaloniaProperty.Register(nameof(PageTransition),
+ new CrossFade(TimeSpan.FromSeconds(0.125)));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty CurrentContentProperty =
+ AvaloniaProperty.RegisterDirect(nameof(CurrentContent),
+ o => o.CurrentContent);
+
+ ///
+ /// Gets or sets the animation played when content appears and disappears.
+ ///
+ public IPageTransition? PageTransition
+ {
+ get => GetValue(PageTransitionProperty);
+ set => SetValue(PageTransitionProperty, value);
+ }
+
+ ///
+ /// Gets the content currently displayed on the screen.
+ ///
+ public object? CurrentContent
+ {
+ get => _currentContent;
+ private set => SetAndRaise(CurrentContentProperty, ref _currentContent, value);
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content));
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromVisualTree(e);
+
+ _lastTransitionCts?.Cancel();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == ContentProperty)
+ {
+ Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content));
+ }
+ }
+
+ ///
+ /// Updates the content with transitions.
+ ///
+ /// New content to set.
+ private async void UpdateContentWithTransition(object? content)
+ {
+ if (VisualRoot is null)
+ {
+ return;
+ }
+
+ _lastTransitionCts?.Cancel();
+ _lastTransitionCts = new CancellationTokenSource();
+
+ if (PageTransition != null)
+ await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
+
+ CurrentContent = content;
+
+ if (PageTransition != null)
+ await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
+ }
+}
diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs
index c4dd79f468..d26e90b2da 100644
--- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs
+++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs
@@ -10,6 +10,7 @@ namespace Avalonia.ReactiveUI
///
/// A ContentControl that animates the transition when its content is changed.
///
+ [Obsolete("Use TransitioningContentControl in Avalonia.Controls namespace")]
public class TransitioningContentControl : ContentControl, IStyleable
{
///
diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml
index 4ae9ea4812..54cbd4faa1 100644
--- a/src/Avalonia.Themes.Default/DefaultTheme.xaml
+++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml
@@ -38,6 +38,7 @@
+
diff --git a/src/Avalonia.Themes.Default/TransitioningContentControl.xaml b/src/Avalonia.Themes.Default/TransitioningContentControl.xaml
new file mode 100644
index 0000000000..6a4d56ccb7
--- /dev/null
+++ b/src/Avalonia.Themes.Default/TransitioningContentControl.xaml
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
index 16e05ffdfd..4d62f8bcff 100644
--- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
@@ -37,6 +37,7 @@
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml b/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml
new file mode 100644
index 0000000000..6a4d56ccb7
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs
index 7d033ccf61..8badc018e0 100644
--- a/src/Avalonia.Visuals/Animation/PageSlide.cs
+++ b/src/Avalonia.Visuals/Animation/PageSlide.cs
@@ -79,6 +79,7 @@ namespace Avalonia.Animation
var animation = new Animation
{
Easing = SlideOutEasing,
+ FillMode = FillMode.Forward,
Children =
{
new KeyFrame
@@ -109,6 +110,7 @@ namespace Avalonia.Animation
to.IsVisible = true;
var animation = new Animation
{
+ FillMode = FillMode.Forward,
Easing = SlideInEasing,
Children =
{