diff --git a/components/Shimmer/OpenSolution.bat b/components/Shimmer/OpenSolution.bat
new file mode 100644
index 000000000..24dc1cb49
--- /dev/null
+++ b/components/Shimmer/OpenSolution.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+
+powershell ..\..\common\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
\ No newline at end of file
diff --git a/components/Shimmer/samples/Assets/Owl.jpg b/components/Shimmer/samples/Assets/Owl.jpg
new file mode 100644
index 000000000..1c8f39f70
Binary files /dev/null and b/components/Shimmer/samples/Assets/Owl.jpg differ
diff --git a/components/Shimmer/samples/Dependencies.props b/components/Shimmer/samples/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/Shimmer/samples/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Shimmer/samples/MultiTarget.props b/components/Shimmer/samples/MultiTarget.props
new file mode 100644
index 000000000..67f1c2743
--- /dev/null
+++ b/components/Shimmer/samples/MultiTarget.props
@@ -0,0 +1,9 @@
+
+
+
+ uwp;wasdk;
+
+
diff --git a/components/Shimmer/samples/Shimmer.Samples.csproj b/components/Shimmer/samples/Shimmer.Samples.csproj
new file mode 100644
index 000000000..7d86b0dd9
--- /dev/null
+++ b/components/Shimmer/samples/Shimmer.Samples.csproj
@@ -0,0 +1,16 @@
+
+
+ Shimmer
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/components/Shimmer/samples/Shimmer.md b/components/Shimmer/samples/Shimmer.md
new file mode 100644
index 000000000..d74d3249f
--- /dev/null
+++ b/components/Shimmer/samples/Shimmer.md
@@ -0,0 +1,17 @@
+---
+title: Shimmer
+author: JustinXinLiu
+description: An easy to use Shimmer control to indicate something is loading.
+keywords: Shimmer, Loading, Control, Layout
+dev_langs:
+ - csharp
+category: Controls
+subcategory: Layout
+discussion-id: 381
+issue-id: 390
+---
+# Shimmer
+
+The Shimmer control can be used to communicate to the user a certain UI element is fetching data or is loading. `Duration` can be set to set the length of the animation.
+
+> [!SAMPLE ShimmerSample]
diff --git a/components/Shimmer/samples/ShimmerSample.xaml b/components/Shimmer/samples/ShimmerSample.xaml
new file mode 100644
index 000000000..d63a64289
--- /dev/null
+++ b/components/Shimmer/samples/ShimmerSample.xaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Shimmer/samples/ShimmerSample.xaml.cs b/components/Shimmer/samples/ShimmerSample.xaml.cs
new file mode 100644
index 000000000..baed12518
--- /dev/null
+++ b/components/Shimmer/samples/ShimmerSample.xaml.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace ShimmerExperiment.Samples;
+
+[ToolkitSampleBoolOption("HasLoaded", false, Title = "Content loaded")]
+
+[ToolkitSample(id: nameof(ShimmerSample), "Basic Shimmer", description: "A sample that shows how to use a shimmer loading indicator.")]
+public sealed partial class ShimmerSample : Page
+{
+ public ShimmerSample()
+ {
+ this.InitializeComponent();
+ }
+
+ private static Visibility ReverseVisibility(Visibility vis) => vis switch
+ {
+ Visibility.Collapsed => Visibility.Visible,
+ Visibility.Visible => Visibility.Collapsed,
+ _ => throw new System.NotImplementedException(),
+
+ };
+}
diff --git a/components/Shimmer/src/AdditionalAssemblyInfo.cs b/components/Shimmer/src/AdditionalAssemblyInfo.cs
new file mode 100644
index 000000000..9796b0d87
--- /dev/null
+++ b/components/Shimmer/src/AdditionalAssemblyInfo.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+// These `InternalsVisibleTo` calls are intended to make it easier for
+// for any internal code to be testable in all the different test projects
+// used with the Labs infrastructure.
+[assembly: InternalsVisibleTo("Shimmer.Tests.Uwp")]
+[assembly: InternalsVisibleTo("Shimmer.Tests.WinAppSdk")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")]
diff --git a/components/Shimmer/src/CommunityToolkit.Labs.WinUI.Shimmer.csproj b/components/Shimmer/src/CommunityToolkit.Labs.WinUI.Shimmer.csproj
new file mode 100644
index 000000000..ed6cb227b
--- /dev/null
+++ b/components/Shimmer/src/CommunityToolkit.Labs.WinUI.Shimmer.csproj
@@ -0,0 +1,17 @@
+
+
+ Shimmer
+ CommunityToolkit.Labs.$(PackageIdVariant).Shimmer
+ This package contains Shimmer.
+ 0.0.1
+
+
+ CommunityToolkit.Labs.WinUI.ShimmerRns
+
+
+
+
+
+
+
+
diff --git a/components/Shimmer/src/Dependencies.props b/components/Shimmer/src/Dependencies.props
new file mode 100644
index 000000000..effa38f34
--- /dev/null
+++ b/components/Shimmer/src/Dependencies.props
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Shimmer/src/MultiTarget.props b/components/Shimmer/src/MultiTarget.props
new file mode 100644
index 000000000..18f6c7c98
--- /dev/null
+++ b/components/Shimmer/src/MultiTarget.props
@@ -0,0 +1,9 @@
+
+
+
+ uwp;wasdk;
+
+
diff --git a/components/Shimmer/src/Shimmer/Shimmer.Properties.cs b/components/Shimmer/src/Shimmer/Shimmer.Properties.cs
new file mode 100644
index 000000000..b1f4d978e
--- /dev/null
+++ b/components/Shimmer/src/Shimmer/Shimmer.Properties.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.Labs.WinUI;
+
+public partial class Shimmer : Control
+{
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty DurationProperty = DependencyProperty.Register(
+ nameof(Duration),
+ typeof(object),
+ typeof(Shimmer),
+ new PropertyMetadata(defaultValue: TimeSpan.FromMilliseconds(1600), PropertyChanged));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(
+ nameof(IsActive),
+ typeof(bool),
+ typeof(Shimmer),
+ new PropertyMetadata(defaultValue: true, PropertyChanged));
+
+
+ ///
+ /// Gets or sets the animation duration
+ ///
+ public TimeSpan Duration
+ {
+ get => (TimeSpan)GetValue(DurationProperty);
+ set => SetValue(DurationProperty, value);
+ }
+
+ ///
+ /// Gets or sets if the animation is playing
+ ///
+ public bool IsActive
+ {
+ get => (bool)GetValue(IsActiveProperty);
+ set => SetValue(IsActiveProperty, value);
+ }
+
+ private static void PropertyChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
+ {
+ var self = (Shimmer)s;
+ if (self.IsActive)
+ {
+ self.StopAnimation();
+ self.TryStartAnimation();
+ }
+ else
+ {
+ self.StopAnimation();
+ }
+ }
+}
diff --git a/components/Shimmer/src/Shimmer/Shimmer.cs b/components/Shimmer/src/Shimmer/Shimmer.cs
new file mode 100644
index 000000000..64e074156
--- /dev/null
+++ b/components/Shimmer/src/Shimmer/Shimmer.cs
@@ -0,0 +1,224 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using Windows.UI;
+#if WINAPPSDK
+using Microsoft.UI;
+using CommunityToolkit.WinUI.UI;
+using CommunityToolkit.WinUI.UI.Animations.Expressions;
+using CommunityToolkit.WinUI.UI.Animations;
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+using Microsoft.UI.Xaml.Shapes;
+#else
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+using Windows.UI.Xaml.Shapes;
+using Microsoft.Toolkit.Uwp.UI;
+using Microsoft.Toolkit.Uwp.UI.Animations.Expressions;
+using Microsoft.Toolkit.Uwp.UI.Animations;
+#endif
+
+namespace CommunityToolkit.Labs.WinUI;
+
+///
+/// A generic shimmer control that can be used to construct a beautiful loading effect.
+///
+[TemplatePart(Name = PART_Shape, Type = typeof(Rectangle))]
+public partial class Shimmer : Control
+{
+ private const float InitialStartPointX = -7.92f;
+ private const string PART_Shape = "Shape";
+
+ private Vector2Node? _sizeAnimation;
+ private Vector2KeyFrameAnimation? _gradientStartPointAnimation;
+ private Vector2KeyFrameAnimation? _gradientEndPointAnimation;
+ private CompositionColorGradientStop? _gradientStop1;
+ private CompositionColorGradientStop? _gradientStop2;
+ private CompositionColorGradientStop? _gradientStop3;
+ private CompositionColorGradientStop? _gradientStop4;
+ private CompositionRoundedRectangleGeometry? _rectangleGeometry;
+ private ShapeVisual? _shapeVisual;
+ private CompositionLinearGradientBrush? _shimmerMaskGradient;
+ private Border? _shape;
+
+ private bool _initialized;
+ private bool _animationStarted;
+
+ public Shimmer()
+ {
+ DefaultStyleKey = typeof(Shimmer);
+ Loaded += OnLoaded;
+ Unloaded += OnUnloaded;
+ }
+
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ _shape = GetTemplateChild(PART_Shape) as Border;
+ if (_initialized is false && TryInitializationResource() && IsActive)
+ {
+ TryStartAnimation();
+ }
+ }
+
+ private void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ if (_initialized is false && TryInitializationResource() && IsActive)
+ {
+ TryStartAnimation();
+ }
+
+ ActualThemeChanged += OnActualThemeChanged;
+ }
+
+ private void OnUnloaded(object sender, RoutedEventArgs e)
+ {
+ ActualThemeChanged -= OnActualThemeChanged;
+ StopAnimation();
+
+ if (_initialized && _shape != null)
+ {
+ ElementCompositionPreview.SetElementChildVisual(_shape, null);
+
+ _rectangleGeometry!.Dispose();
+ _shapeVisual!.Dispose();
+ _shimmerMaskGradient!.Dispose();
+ _gradientStop1!.Dispose();
+ _gradientStop2!.Dispose();
+ _gradientStop3!.Dispose();
+ _gradientStop4!.Dispose();
+
+ _initialized = false;
+ }
+ }
+
+ private void OnActualThemeChanged(FrameworkElement sender, object args)
+ {
+ if (_initialized is false)
+ {
+ return;
+ }
+
+ SetGradientStopColorsByTheme();
+ }
+
+ private bool TryInitializationResource()
+ {
+ if (_initialized)
+ {
+ return true;
+ }
+
+ if (_shape is null || IsLoaded is false)
+ {
+ return false;
+ }
+
+ var compositor = _shape.GetVisual().Compositor;
+
+ _rectangleGeometry = compositor.CreateRoundedRectangleGeometry();
+ _shapeVisual = compositor.CreateShapeVisual();
+ _shimmerMaskGradient = compositor.CreateLinearGradientBrush();
+ _gradientStop1 = compositor.CreateColorGradientStop();
+ _gradientStop2 = compositor.CreateColorGradientStop();
+ _gradientStop3 = compositor.CreateColorGradientStop();
+ _gradientStop4 = compositor.CreateColorGradientStop();
+ SetGradientAndStops();
+ SetGradientStopColorsByTheme();
+ _rectangleGeometry.CornerRadius = new Vector2((float)CornerRadius.TopLeft);
+ var spriteShape = compositor.CreateSpriteShape(_rectangleGeometry);
+ spriteShape.FillBrush = _shimmerMaskGradient;
+ _shapeVisual.Shapes.Add(spriteShape);
+ ElementCompositionPreview.SetElementChildVisual(_shape, _shapeVisual);
+
+ _initialized = true;
+ return true;
+ }
+
+ private void SetGradientAndStops()
+ {
+ _shimmerMaskGradient!.StartPoint = new Vector2(InitialStartPointX, 0.0f);
+ _shimmerMaskGradient.EndPoint = new Vector2(0.0f, 1.0f); //Vector2.One
+
+ _gradientStop1!.Offset = 0.273f;
+ _gradientStop2!.Offset = 0.436f;
+ _gradientStop3!.Offset = 0.482f;
+ _gradientStop4!.Offset = 0.643f;
+
+ _shimmerMaskGradient.ColorStops.Add(_gradientStop1);
+ _shimmerMaskGradient.ColorStops.Add(_gradientStop2);
+ _shimmerMaskGradient.ColorStops.Add(_gradientStop3);
+ _shimmerMaskGradient.ColorStops.Add(_gradientStop4);
+ }
+
+ private void SetGradientStopColorsByTheme()
+ {
+ switch (ActualTheme)
+ {
+ case ElementTheme.Default:
+ case ElementTheme.Dark:
+ _gradientStop1!.Color = Color.FromArgb((byte)(255 * 3.26 / 100), 255, 255, 255);
+ _gradientStop2!.Color = Color.FromArgb((byte)(255 * 6.05 / 100), 255, 255, 255);
+ _gradientStop3!.Color = Color.FromArgb((byte)(255 * 6.05 / 100), 255, 255, 255);
+ _gradientStop4!.Color = Color.FromArgb((byte)(255 * 3.26 / 100), 255, 255, 255);
+ break;
+ case ElementTheme.Light:
+ _gradientStop1!.Color = Color.FromArgb((byte)(255 * 5.37 / 100), 0, 0, 0);
+ _gradientStop2!.Color = Color.FromArgb((byte)(255 * 2.89 / 100), 0, 0, 0);
+ _gradientStop3!.Color = Color.FromArgb((byte)(255 * 2.89 / 100), 0, 0, 0);
+ _gradientStop4!.Color = Color.FromArgb((byte)(255 * 5.37 / 100), 0, 0, 0);
+ break;
+ }
+ }
+
+ private void TryStartAnimation()
+ {
+ if (_animationStarted || _initialized is false || _shape is null)
+ {
+ return;
+ }
+
+ var rootVisual = _shape.GetVisual();
+ _sizeAnimation = rootVisual.GetReference().Size;
+ _shapeVisual.StartAnimation(nameof(ShapeVisual.Size), _sizeAnimation);
+ _rectangleGeometry.StartAnimation(nameof(CompositionRoundedRectangleGeometry.Size), _sizeAnimation);
+
+ _gradientStartPointAnimation = rootVisual.Compositor.CreateVector2KeyFrameAnimation();
+ _gradientStartPointAnimation.Duration = Duration;
+ _gradientStartPointAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
+ _gradientStartPointAnimation.InsertKeyFrame(0.0f, new Vector2(InitialStartPointX, 0.0f));
+ _gradientStartPointAnimation.InsertKeyFrame(1.0f, Vector2.Zero);
+ _shimmerMaskGradient!.StartAnimation(nameof(CompositionLinearGradientBrush.StartPoint), _gradientStartPointAnimation);
+
+ _gradientEndPointAnimation = rootVisual.Compositor.CreateVector2KeyFrameAnimation();
+ _gradientEndPointAnimation.Duration = Duration;
+ _gradientEndPointAnimation.IterationBehavior = AnimationIterationBehavior.Forever;
+ _gradientEndPointAnimation.InsertKeyFrame(0.0f, new Vector2(1.0f, 0.0f)); //Vector2.One
+ _gradientEndPointAnimation.InsertKeyFrame(1.0f, new Vector2(-InitialStartPointX, 1.0f));
+ _shimmerMaskGradient.StartAnimation(nameof(CompositionLinearGradientBrush.EndPoint), _gradientEndPointAnimation);
+
+ _animationStarted = true;
+ }
+
+ private void StopAnimation()
+ {
+ if (_animationStarted is false)
+ {
+ return;
+ }
+
+ _shapeVisual!.StopAnimation(nameof(ShapeVisual.Size));
+ _rectangleGeometry!.StopAnimation(nameof(CompositionRoundedRectangleGeometry.Size));
+ _shimmerMaskGradient!.StopAnimation(nameof(CompositionLinearGradientBrush.StartPoint));
+ _shimmerMaskGradient.StopAnimation(nameof(CompositionLinearGradientBrush.EndPoint));
+
+ _sizeAnimation!.Dispose();
+ _gradientStartPointAnimation!.Dispose();
+ _gradientEndPointAnimation!.Dispose();
+ _animationStarted = false;
+ }
+}
diff --git a/components/Shimmer/src/Shimmer/Shimmer.xaml b/components/Shimmer/src/Shimmer/Shimmer.xaml
new file mode 100644
index 000000000..4dd692be4
--- /dev/null
+++ b/components/Shimmer/src/Shimmer/Shimmer.xaml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Shimmer/src/Themes/Generic.xaml b/components/Shimmer/src/Themes/Generic.xaml
new file mode 100644
index 000000000..727df06d0
--- /dev/null
+++ b/components/Shimmer/src/Themes/Generic.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/components/Shimmer/tests/ExampleShimmerTestClass.cs b/components/Shimmer/tests/ExampleShimmerTestClass.cs
new file mode 100644
index 000000000..7e5dc44dd
--- /dev/null
+++ b/components/Shimmer/tests/ExampleShimmerTestClass.cs
@@ -0,0 +1,132 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Tooling.TestGen;
+using CommunityToolkit.Tests;
+
+namespace ShimmerExperiment.Tests;
+
+[TestClass]
+public partial class ExampleShimmerTestClass : VisualUITestBase
+{
+ // If you don't need access to UI objects directly or async code, use this pattern.
+ [TestMethod]
+ public void SimpleSynchronousExampleTest()
+ {
+ var assembly = typeof(Shimmer).Assembly;
+ var type = assembly.GetType(typeof(Shimmer).FullName ?? string.Empty);
+
+ Assert.IsNotNull(type, "Could not find Shimmer type.");
+ Assert.AreEqual(typeof(Shimmer), type, "Type of Shimmer does not match expected type.");
+ }
+
+ // If you don't need access to UI objects directly, use this pattern.
+ [TestMethod]
+ public async Task SimpleAsyncExampleTest()
+ {
+ await Task.Delay(250);
+
+ Assert.IsTrue(true);
+ }
+
+ // Example that shows how to check for exception throwing.
+ [TestMethod]
+ public void SimpleExceptionCheckTest()
+ {
+ // If you need to check exceptions occur for invalid inputs, etc...
+ // Use Assert.ThrowsException to limit the scope to where you expect the error to occur.
+ // Otherwise, using the ExpectedException attribute could swallow or
+ // catch other issues in setup code.
+ Assert.ThrowsException(() => throw new NotImplementedException());
+ }
+
+ // The UIThreadTestMethod automatically dispatches to the UI for us to work with UI objects.
+ [UIThreadTestMethod]
+ public void SimpleUIAttributeExampleTest()
+ {
+ var component = new Shimmer();
+ Assert.IsNotNull(component);
+ }
+
+ // The UIThreadTestMethod can also easily grab a XAML Page for us by passing its type as a parameter.
+ // This lets us actually test a control as it would behave within an actual application.
+ // The page will already be loaded by the time your test is called.
+ [UIThreadTestMethod]
+ public void SimpleUIExamplePageTest(ExampleShimmerTestPage page)
+ {
+ // You can use the Toolkit Visual Tree helpers here to find the component by type or name:
+ var component = page.FindDescendant();
+
+ Assert.IsNotNull(component);
+
+ var componentByName = page.FindDescendant("ShimmerControl");
+
+ Assert.IsNotNull(componentByName);
+ }
+
+ // You can still do async work with a UIThreadTestMethod as well.
+ [UIThreadTestMethod]
+ public async Task SimpleAsyncUIExamplePageTest(ExampleShimmerTestPage page)
+ {
+ // This helper can be used to wait for a rendering pass to complete.
+ await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });
+
+ var component = page.FindDescendant();
+
+ Assert.IsNotNull(component);
+ }
+
+ //// ----------------------------- ADVANCED TEST SCENARIOS -----------------------------
+
+ // If you need to use DataRow, you can use this pattern with the UI dispatch still.
+ // Otherwise, checkout the UIThreadTestMethod attribute above.
+ // See https://github.com/CommunityToolkit/Labs-Windows/issues/186
+ [TestMethod]
+ public async Task ComplexAsyncUIExampleTest()
+ {
+ await EnqueueAsync(() =>
+ {
+ var component = new Shimmer();
+ Assert.IsNotNull(component);
+ });
+ }
+
+ // If you want to load other content not within a XAML page using the UIThreadTestMethod above.
+ // Then you can do that using the Load/UnloadTestContentAsync methods.
+ [TestMethod]
+ public async Task ComplexAsyncLoadUIExampleTest()
+ {
+ await EnqueueAsync(async () =>
+ {
+ var component = new Shimmer();
+ Assert.IsNotNull(component);
+ Assert.IsFalse(component.IsLoaded);
+
+ await LoadTestContentAsync(component);
+
+ Assert.IsTrue(component.IsLoaded);
+
+ await UnloadTestContentAsync(component);
+
+ Assert.IsFalse(component.IsLoaded);
+ });
+ }
+
+ // You can still use the UIThreadTestMethod to remove the extra layer for the dispatcher as well:
+ [UIThreadTestMethod]
+ public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest()
+ {
+ var component = new Shimmer();
+ Assert.IsNotNull(component);
+ Assert.IsFalse(component.IsLoaded);
+
+ await LoadTestContentAsync(component);
+
+ Assert.IsTrue(component.IsLoaded);
+
+ await UnloadTestContentAsync(component);
+
+ Assert.IsFalse(component.IsLoaded);
+ }
+}
diff --git a/components/Shimmer/tests/ExampleShimmerTestPage.xaml b/components/Shimmer/tests/ExampleShimmerTestPage.xaml
new file mode 100644
index 000000000..118dbd063
--- /dev/null
+++ b/components/Shimmer/tests/ExampleShimmerTestPage.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/components/Shimmer/tests/ExampleShimmerTestPage.xaml.cs b/components/Shimmer/tests/ExampleShimmerTestPage.xaml.cs
new file mode 100644
index 000000000..151241198
--- /dev/null
+++ b/components/Shimmer/tests/ExampleShimmerTestPage.xaml.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace ShimmerExperiment.Tests;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+public sealed partial class ExampleShimmerTestPage : Page
+{
+ public ExampleShimmerTestPage()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/Shimmer/tests/Shimmer.Tests.projitems b/components/Shimmer/tests/Shimmer.Tests.projitems
new file mode 100644
index 000000000..bff096a9f
--- /dev/null
+++ b/components/Shimmer/tests/Shimmer.Tests.projitems
@@ -0,0 +1,23 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ 02EDA7A2-0AD0-449E-AD75-E9B363620679
+
+
+ ShimmerExperiment.Tests
+
+
+
+
+ ExampleShimmerTestPage.xaml
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+
\ No newline at end of file
diff --git a/components/Shimmer/tests/Shimmer.Tests.shproj b/components/Shimmer/tests/Shimmer.Tests.shproj
new file mode 100644
index 000000000..4e8c9e5ef
--- /dev/null
+++ b/components/Shimmer/tests/Shimmer.Tests.shproj
@@ -0,0 +1,13 @@
+
+
+
+ 02EDA7A2-0AD0-449E-AD75-E9B363620679
+ 14.0
+
+
+
+
+
+
+
+