Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration!How!?

SkeletonView

A content-wrapping control that shows animated shimmer placeholders while data is loading — conceptually similar to MAUI’s RefreshView. You wrap your real content and bind IsBusy; while it is true the content is hidden and shimmer placeholders are shown in its place. When IsBusy becomes false, the placeholders disappear and the real content is shown.

The built-in placeholder is a stack of shimmer “lines” (great for text/paragraph content). For anything else, supply a custom placeholder that matches the shape of the content being loaded — SkeletonTemplate on MAUI, SkeletonContent on Blazor.

  • NuGet downloads for Shiny.Maui.Controls
  • NuGet downloads for Shiny.Blazor.Controls
Frameworks
.NET MAUI
Blazor
Loading Loaded
Line and card skeleton placeholders while IsBusy is true The same layout once the real content has loaded
  • Wraps a content area and toggles between content and placeholders with IsBusy
  • Built-in line placeholders (configurable count, height, spacing, corner radius, color)
  • Custom placeholder layout via SkeletonTemplate (MAUI) / SkeletonContent (Blazor)
  • Animated shimmer sweep — a translating LinearGradientBrush band on MAUI, an animated CSS gradient on Blazor. On both hosts the sweep is masked to the placeholder shapes, so the highlight never crosses the gaps between them or the empty box around them
  • Shimmer honors prefers-reduced-motion on Blazor
  • Shimmer can be disabled for static placeholders (ShimmerEnabled)

The child element is the Content (it is the ContentProperty), so you can nest it directly — no need to write <shiny:SkeletonView.Content> unless you also set SkeletonTemplate.

<shiny:SkeletonView xmlns:shiny="http://shiny.net/maui/controls"
IsBusy="{Binding IsBusy}"
ItemCount="4">
<VerticalStackLayout Spacing="8">
<Label Text="Loaded Article" FontSize="16" FontAttributes="Bold" />
<Label Text="This real content appears once loading finishes." />
</VerticalStackLayout>
</shiny:SkeletonView>

MAUI — Custom Placeholder (match the content shape)

Section titled “MAUI — Custom Placeholder (match the content shape)”
<shiny:SkeletonView IsBusy="{Binding IsBusy}"
BaseColor="{AppThemeBinding Light=#E1E1E6, Dark=#242B32}">
<shiny:SkeletonView.SkeletonTemplate>
<DataTemplate>
<HorizontalStackLayout Spacing="12">
<BoxView WidthRequest="56" HeightRequest="56" CornerRadius="28" Color="{AppThemeBinding Light=#E1E1E6, Dark=#242B32}" />
<VerticalStackLayout Spacing="10" VerticalOptions="Center" WidthRequest="200">
<BoxView HeightRequest="14" CornerRadius="6" Color="{AppThemeBinding Light=#E1E1E6, Dark=#242B32}" HorizontalOptions="Fill" />
<BoxView HeightRequest="14" CornerRadius="6" Color="{AppThemeBinding Light=#E1E1E6, Dark=#242B32}" WidthRequest="120" HorizontalOptions="Start" />
</VerticalStackLayout>
</HorizontalStackLayout>
</DataTemplate>
</shiny:SkeletonView.SkeletonTemplate>
<shiny:SkeletonView.Content>
<HorizontalStackLayout Spacing="12">
<BoxView WidthRequest="56" HeightRequest="56" CornerRadius="28" Color="#7C3AED" />
<VerticalStackLayout Spacing="4" VerticalOptions="Center">
<Label Text="Allan Ritchie" FontSize="16" FontAttributes="Bold" />
<Label Text="Shiny Controls maintainer" TextColor="#6B7280" />
</VerticalStackLayout>
</HorizontalStackLayout>
</shiny:SkeletonView.Content>
</shiny:SkeletonView>

Use BaseColor for the placeholder fill in your SkeletonTemplate so it matches the built-in look.

Blazor — Default Placeholder (text lines)

Section titled “Blazor — Default Placeholder (text lines)”
<SkeletonView IsBusy="@isBusy" ItemCount="4">
<ChildContent>
<h3>Loaded Article</h3>
<p>This real content appears once loading finishes.</p>
</ChildContent>
</SkeletonView>

Add the shiny-skeleton__shape class to any element in SkeletonContent to give it the shimmer. The CSS custom properties for the colors/duration are inherited from the root, so your shapes shimmer automatically.

<SkeletonView IsBusy="@isBusy">
<SkeletonContent>
<div style="display:flex; gap:12px; align-items:center;">
<div class="shiny-skeleton__shape" style="width:56px; height:56px; border-radius:50%;"></div>
<div style="display:flex; flex-direction:column; gap:10px; flex:1;">
<div class="shiny-skeleton__shape" style="height:14px; border-radius:6px; width:60%;"></div>
<div class="shiny-skeleton__shape" style="height:14px; border-radius:6px; width:40%;"></div>
</div>
</div>
</SkeletonContent>
<ChildContent>
<!-- real content -->
</ChildContent>
</SkeletonView>
Property Type Default Description
Content View? null The real content shown when IsBusy is false (the ContentProperty)
IsBusy bool false When true, hides content and shows animated placeholders
SkeletonTemplate DataTemplate? null Custom placeholder layout; when null, built-in lines are used
ItemCount int 3 Number of built-in placeholder lines (last line is shortened)
BaseColor Color? null (theme SurfaceContainerHigh) Fill color of the built-in placeholder shapes
ShimmerColor Color? null (BaseColor lightened ~10%) Color of the sweeping highlight
ShimmerEnabled bool true When false, placeholders are static (no sweep)
AnimationDuration uint 1200 Duration (ms) of a single shimmer sweep
CornerRadius double 6 Corner radius of the built-in placeholder lines
ItemHeight double 16 Height of each built-in placeholder line
ItemSpacing double 12 Vertical spacing between built-in placeholder lines
Parameter Type Default Description
IsBusy bool false When true, hides content and shows placeholders
ChildContent RenderFragment? The real content shown when IsBusy is false
SkeletonContent RenderFragment? Custom placeholder markup; when null, built-in lines are used
ItemCount int 3 Number of built-in placeholder lines
ItemHeight double 16 Height (px) of each built-in placeholder line
ItemSpacing double 12 Vertical spacing (px) between built-in lines
CornerRadius double 6 Corner radius (px) of the built-in placeholder lines
BaseColor string #e1e1e6 Base fill color of placeholder shapes
HighlightColor string rgba(255,255,255,0.6) Color of the sweeping highlight
AnimationDuration double 1.4 Duration (seconds) of a single shimmer sweep
ShimmerEnabled bool true When false, placeholders are static (no sweep)
CssClass string? null Additional CSS class on the root
  • SkeletonView swaps between content and placeholders by visibility, so the control’s height follows whichever is shown — size your placeholder to roughly match the loaded content to avoid layout jumps.
  • For a full-screen “loading the whole page” experience use Overlay & LoadingOverlay instead — SkeletonView is for inline content regions.
  • The default ShimmerColor is derived from BaseColor, so when you supply a SkeletonTemplate give its shapes that same fill. Hardcoding a light grey into the template while the theme (and therefore the sheen) is dark makes the sweep darken the shapes instead of highlighting them — prefer an AppThemeBinding over a fixed hex.
  • Both hosts mirror the same API: IsBusy, a custom placeholder slot, item count/height/spacing, base/highlight colors, animation duration, and a shimmer on/off toggle.