HorusStudio.Maui.Skeleton 3.0.0

Prefix Reserved
dotnet add package HorusStudio.Maui.Skeleton --version 3.0.0
                    
NuGet\Install-Package HorusStudio.Maui.Skeleton -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="HorusStudio.Maui.Skeleton" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HorusStudio.Maui.Skeleton" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="HorusStudio.Maui.Skeleton" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add HorusStudio.Maui.Skeleton --version 3.0.0
                    
#r "nuget: HorusStudio.Maui.Skeleton, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package HorusStudio.Maui.Skeleton@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=HorusStudio.Maui.Skeleton&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=HorusStudio.Maui.Skeleton&version=3.0.0
                    
Install as a Cake Tool

Skeleton for Xamarin and MAUI apps

<img src="https://raw.githubusercontent.com/HorusSoftwareUY/Xamarin.Forms.Skeleton/master/icon.png" width="128">

The Skeleton control is a popular approach to loading content in mobile apps that provides one or more visual placeholders while content is being loaded. This technique is particularly useful for improving user experience, as it reduces perceived load times and provides a more engaging experience.

Playground Try it live AI Skill skill.md

Playground | Find the animation that fits your app

Six layouts, seven animations and every property we expose. Switch between them and keep the one that belongs in your .NET MAUI project. After installing the NuGet package, you can use your own colors and apply the animations to any layout.

horus.com.uy/skeleton

<img src="https://raw.githubusercontent.com/HorusSoftwareUY/Xamarin.Forms.Skeleton/master/screenshots/Skeleton.gif" width="800">

Setup

.NET MAUI

Platform Version
.NET 8, 9 and 10
Android, iOS, macOS, Windows all versions supported by .NET MAUI

Version 3.0.0 and later require .NET 8 or newer. Apps on .NET 6 or 7 should stay on 2.0.0, which keeps working.

Xamarin.Forms (legacy)

Xamarin.Forms reached end of support in May 2024. This package is frozen at 2.0.0 and will not receive further updates. New work happens on the .NET MAUI package above.

Platform Version
Xamarin.iOS iOS 8+
Xamarin.Android API 16+
Xamarin.Forms >= 4.0.0.425677

Agent skill

Rather than learning the API, you can have a coding agent apply it for you. The skill and the two ways to use it are in skills/.

Usage

You must add this namespace to your xaml files.

For .NET MAUI:

xmlns:sk="clr-namespace:Maui.Skeleton;assembly=Maui.Skeleton"

For Xamarin.Forms:

xmlns:sk="clr-namespace:Xamarin.Forms.Skeleton;assembly=Xamarin.Forms.Skeleton"

Add the following properties to generate a loading animation:

<ListView RowHeight="90"
	SeparatorVisibility="None"
	SelectionMode="None"
	VerticalOptions="FillAndExpand"
	BackgroundColor="Transparent"
	ItemTemplate="{StaticResource ItemTemplate}"
	ItemsSource="{Binding Items}"
	sk:Skeleton.IsParent="True"
	sk:Skeleton.IsBusy="{Binding IsBusy}"
	sk:Skeleton.Animation="{sk:DefaultAnimation Fade}" />

Add the following properties to set a loading animation with a specific background color:

<Frame BackgroundColor="Transparent"
       HasShadow="False"
       CornerRadius="5"
       Padding="0"
       HorizontalOptions="Start"
       sk:Skeleton.IsBusy="{Binding IsBusy}"
       sk:Skeleton.BackgroundColor="#c6c6c5">
			<Label Text="{Binding Title}"
					TextColor="#000000"
					FontSize="20"
					FontAttributes="Bold"
					HorizontalOptions="Start"/>
</Frame>

Properties

IsBusy (Boolean)
  • Indicates if the control is busy in a loading state.
  • The default value is false.

IsBusy is not inherited. Every element that should react needs its own sk:Skeleton.IsBusy="{Binding ...}". Setting it once on an outer layout does nothing for the elements inside it.

IsParent (Boolean)
  • Controls whether a container covers its own content or lets each child draw itself.
  • The default value is false.

The name reads backwards from what it does. IsParent="True" does not mean "handle my children" — it means "leave my children alone, they handle themselves".

What happens
IsParent="False" (default) The container hides its content and paints its own BackgroundColor. You get one solid shape.
IsParent="True" The content is left untouched. Every child that should show a placeholder needs its own IsBusy.

Either way the container still paints its own colour and runs its own animation. IsParent only governs whether it touches what is inside.

One solid shape — no IsParent, and the container needs a colour:

<Border sk:Skeleton.IsBusy="{Binding IsBusy}"
        sk:Skeleton.BackgroundColor="#c6c6c5">
    <Label Text="{Binding Title}" />
</Border>

Several shapes — IsParent="True", and each child declares itself:

<VerticalStackLayout sk:Skeleton.IsParent="True"
                     sk:Skeleton.IsBusy="{Binding IsBusy}">

    <Border sk:Skeleton.IsBusy="{Binding IsBusy}"
            sk:Skeleton.BackgroundColor="#c6c6c5">
        <Label Text="{Binding Title}" />
    </Border>

    <Border sk:Skeleton.IsBusy="{Binding IsBusy}"
            sk:Skeleton.BackgroundColor="#c6c6c5">
        <Label Text="{Binding Subtitle}" />
    </Border>

</VerticalStackLayout>

What changed in 3.0.0. Until 2.0.0 only a Layout, such as Grid or StackLayout, hid its content. A view holding a single piece of content painted the placeholder and left its content showing on top of it, which is why samples and apps used Skeleton.Hide="True" on each child to get out of the way. Those views now behave like every other container, so that workaround is no longer needed. It still works, it is just redundant.

The test is two conditions: a View implementing IContentView, whose presented content is a View. Border, Frame, ContentView, ScrollView, RefreshView and SwipeView are the ones that meet both. Implementing the interface is not enough on its own: a RadioButton does, but with its default template it presents nothing, so nothing of it fades.

If content that used to stay visible now disappears, that container is the one deciding it: set sk:Skeleton.IsParent="True" on it to get the old behaviour back.

Three mistakes this prevents, none of which reports an error:

  • IsParent="True" with a child that declares nothing does nothing at all to that child. If it holds real content, the content shows straight through the loading state. If you drew your own placeholder shape there, it stays on screen after loading finishes, on top of the real content.
  • No IsParent and no BackgroundColor leaves an empty hole: the content is hidden and nothing is painted in its place.
  • IsBusy on an outer layout alone renders no placeholders at all, because it is not inherited.
BackgroundColor (Color)
  • Control background color when is busy.
  • The default value is the xamarin forms default color.
Hide (Boolean)
  • Indicates if the control is hide when is busy.
  • The default value is false.
Animation (BaseAnimation)
  • Control animation when is busy.
  • Possible values: None, Fade, Beat, HorizontalShake, VerticalShake, Shimmer, Aurora, Tint and custom animation inheriting from BaseAnimation.
  • The default value is null, which means no animation runs.

Animation settings

The interval and the parameter are not attached properties. They are set on the DefaultAnimation markup extension, alongside Source:

sk:Skeleton.Animation="{sk:DefaultAnimation Source=Fade, Interval=600, Parameter=0.3}"

Source is the extension's content property, so {sk:DefaultAnimation Fade} is shorthand for {sk:DefaultAnimation Source=Fade}.

Setting Meaning Default
Source Which built-in animation to use. None resolves to no animation at all. None
Interval Duration in milliseconds of each half of a cycle, so a Fade at 600 takes 1.2s per pulse. 500
Parameter What the animation interpolates towards. See the table below. per animation
Source What moves Parameter means Default
Fade opacity opacity to fade to 0.6
Beat scale scale to grow to 1.03
VerticalShake position offset in units, up and down 15
HorizontalShake position offset in units, left and right 10
Shimmer a band of light across the placeholder not used, see below —
Aurora a wide field of colour drifting back and forth not used, see below —
Tint the whole placeholder washing to a colour and back not used, see below —
Shimmer, Aurora and Tint

These repaint the placeholder rather than animating a property of the view, so they behave a little differently from the other four. Shimmer sends a band of light across and off the other side; Aurora pans a much wider field of colour back and forth, so colour is always on screen; Tint washes the whole placeholder to a colour and back, with nothing moving at all.

  • It must be attached to the element that shows the placeholder colour. It does not carry down to children the way Fade and Beat do.
  • It does not work on Frame. Frame is deprecated in MAUI and its renderer does not repaint when the background is replaced, so the band never moves. Use Border.
  • Interval is the whole movement, not half a cycle: one pass for Shimmer, out and back for Aurora and Tint. 1600 is a good value for any of them.
  • They ignore Parameter, and take Direction and SweepColors instead.
<Border StrokeShape="RoundRectangle 5"
        StrokeThickness="0"
        sk:Skeleton.IsBusy="{Binding IsBusy}"
        sk:Skeleton.BackgroundColor="#c6c6c5"
        sk:Skeleton.Animation="{sk:DefaultAnimation Source=Shimmer, Interval='1600', Direction='Diagonal'}" />
Setting Values Default
Direction Horizontal, Vertical, Diagonal, DiagonalReverse. Not used by Tint. Horizontal
SweepColors two or three #AARRGGBB colours, comma separated and quoted follows the placeholder

Left alone, the band contrasts with the placeholder automatically: light over a dark placeholder, dark over a light one. A placeholder bound with AppThemeBinding therefore shimmers correctly in both themes with no extra work.

Demo

MAUI

https://github.com/HorusSoftwareUY/Xamarin.Forms.Skeleton/tree/master/SkeletonSample

Xamarin.Forms:

https://github.com/HorusSoftwareUY/Xamarin.Forms.Skeleton/tree/master/SkeletonExample

Changelog

What changed in each release, including the behaviour change in 3.0.0 and how to opt out of it, is in CHANGELOG.md.

Developed by

<a href="http://horus.com.uy" ><img src="https://cdn.prod.website-files.com/64a7016392b0b7da3a8604e3/6aa9893608391fb09df87302_horus-github.png" width="128"></a>

Contributions

Contributions are welcome! If you find a bug want a feature added please report it.

If you want to contribute code please file an issue, create a branch, and file a pull request.

License

MIT License - see LICENSE.txt

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on HorusStudio.Maui.Skeleton:

Repository Stars
danielmonettelli/dotnetmaui-mitawi-weather-app-challenge
Mitawi is an open-source weather forecast application developed with .NET MAUI. With its intuitive and user-friendly interface, Mitawi provides accurate and up-to-date weather information for your current location and anywhere around the globe.
Version Downloads Last Updated
3.0.0 614 9/15/2026
2.0.0 307,145 3/21/2023

- Multi-targets .NET 8, 9 and 10, so each app gets a binary built against its own MAUI version.
- Requires .NET 8 or newer. Apps on .NET 6 or 7 should stay on 2.0.0, which keeps working.
- Fixes rapid flickering on devices where the system has animations turned off. The skeleton now settles into a static placeholder instead of spinning the animation loop.
- Behaviour change: a view holding a single piece of content now fades that content while busy, like every Layout already did, and Skeleton.IsParent works on it. The test is two conditions: a View implementing IContentView, whose presented content is a View. Border, Frame, ContentView, ScrollView, RefreshView and SwipeView are the ones that meet both. Implementing the interface is not enough on its own: a RadioButton does, but with its default template it presents nothing, so nothing of it fades. Children previously marked with Skeleton.Hide as a workaround keep working; the attribute is now redundant. If content that used to stay visible through the loading state now disappears, set Skeleton.IsParent="True" on that container to get the old behaviour back.
- Animations are now driven from the UI thread, and a failing animation no longer leaves a view unable to animate again.
- New Shimmer animation: a band of light that sweeps across the placeholder, with a Direction of Horizontal, Vertical, Diagonal or DiagonalReverse and optional SweepColors. Left alone the band contrasts with the placeholder colour, so it works on light and dark themes without configuration. Shimmer paints the element's own background, so it has to be attached to the element showing the placeholder and does not work on the deprecated Frame; use Border.
- New Aurora animation: a wide field of colour that drifts back and forth across the placeholder, sharing Direction and SweepColors with Shimmer. Where Shimmer sends a band across and off the other side, Aurora keeps colour on screen and only shifts it.
- New Tint animation: the whole placeholder washes to a colour and back, with nothing moving. The quietest of the three, for loading states that should stay in the background. It takes the middle colour of SweepColors, so one palette works for all three.
- Fixes placeholders that were never removed. An element with no BackgroundColor of its own kept the grey after loading finished, because the restore wrote back the null that was saved on the way in and writing null does not repaint. A Border wrapping content was the common case; it now goes back to transparent.
- Fixes a Label losing its text permanently. The same null was written back to TextColor, so a Label that never declared one stayed transparent for the rest of the session. Its colour is now read from the native control, so it hides while loading and comes back afterwards, still following the platform's light and dark colours.
- A Button that declares no TextColor of its own is left readable while loading rather than hidden, because its native colour is one per state and restoring a single one would stop a disabled button looking disabled. Give it a TextColor to have its text hidden like any other.
- Still zero dependencies.