This simple component can improve the user experience of your Blazor app, providing a hint of feedback that they aren’t waiting in vain.
Creating smooth experiences in Blazor applications for better interaction should always be a point to consider during their development.
One of these experiences is the feedback given to the user when processing happens behind the scenes, such as when information is being retrieved to fill a page. One of the components that can help us with this task is the Progress Telerik UI for Blazor Skeleton component, which allows you to create loader-like placeholders in the spaces where information will be. Let’s look at its features and how to integrate it into a Blazor application.
Let’s start by analyzing how the Skeleton component works. First of all, you need to configure the Blazor project to work with Telerik components, according to the installation guides.
The next step is to go to the page where you want to use the component, where you can add it via the TelerikSkeleton tag as shown below:
<div style="min-height:100vh; display:flex; align-items:center; justify-content:center;">
<div style="width:240px; height:48px;">
<TelerikSkeleton />
</div>
</div>
In the code above, I have added some styles to center the component on the page, resulting in the following:
Now, you should consider that the component has the following parameters that you can use to configure it:
ShapeType (enum): Allows you to select a predefined shapeAnimationType (enum): Allows you to select a predefined animationVisible (bool): Specifies whether the component should be visible on the page or notWidth (string) and Height (string): Specify the width and height of the componentClass (string): Allows rendering a custom class in the componentFrom the previous properties, you can configure ShapeType with the values SkeletonShapeType.Text (by default), SkeletonShapeType.Rectangle and SkeletonShapeType.Circle, as well as AnimationType with the values SkeletonAnimationType.None, SkeletonAnimationType.Pulse (by default) and SkeletonAnimationType.Wave.
An example of the Skeleton component with these applied properties is as follows:
<TelerikSkeleton
ShapeType="SkeletonShapeType.Circle"
AnimationType="SkeletonAnimationType.Pulse"
Width="100px" Height="100px" Visible="true">
</TelerikSkeleton>
When visualizing the component, it looks as follows:
Although the control is quite simple to use, its power lies in combining several of these components to recreate interfaces where we want to provide feedback to the user about a loading of information, as we will see next.
So far we have seen the features of the Skeleton component. Now, you may be wondering how to integrate it into your application—that is, how to build an interface using several Skeleton components while data is being loaded, and then show the real data once it has been loaded.
To make this more realistic, let’s assume we have created an application using several Blazor components that simulate a social network. This is the homepage:
@page "/feed"
<div class="feed-container">
<h3 class="mb-3">Feed</h3>
<div class="composer card p-3 mb-4">
<div class="d-flex align-items-start gap-3">
<TelerikAvatar Type="AvatarType.Text" Width="48px" Height="48px" Rounded="@ThemeConstants.Avatar.Rounded.Full">HP</TelerikAvatar>
<div class="flex-grow-1">
<TelerikTextArea Rows="3" Placeholder="What's on your mind?" Width="100%" />
<div class="mt-2 d-flex gap-2 justify-content-end">
<TelerikButton ThemeColor="primary">
<TelerikSvgIcon Icon="@SvgIcon.PaperPlane"></TelerikSvgIcon> Post
</TelerikButton>
</div>
</div>
</div>
</div>
<div class="feed">
@foreach (var post in Posts)
{
<TelerikCard Class="mb-4">
<CardHeader>
<div class="d-flex align-items-center gap-3">
<TelerikAvatar Type="AvatarType.Text" Rounded="@ThemeConstants.Avatar.Rounded.Full" Width="48px" Height="48px">@GetInitials(post.UserName)</TelerikAvatar>
<div class="flex-grow-1 w-100">
<div class="fw-semibold">@post.UserName</div>
<div class="text-muted small">@post.PostedAt.ToLocalTime().ToString("g")</div>
</div>
</div>
</CardHeader>
<CardBody>
<p class="mb-3">@post.Content</p>
@if (!string.IsNullOrWhiteSpace(post.ImageUrl))
{
<img src="@post.ImageUrl" alt="post image" class="img-fluid rounded" />
}
</CardBody>
<CardFooter>
<div class="d-flex gap-2">
<TelerikButton ThemeColor="primary" FillMode="Telerik.Blazor.ThemeConstants.Button.FillMode.Outline">
<TelerikSvgIcon Icon="@SvgIcon.Heart"></TelerikSvgIcon> Like
</TelerikButton>
<TelerikButton FillMode="Telerik.Blazor.ThemeConstants.Button.FillMode.Outline">
<TelerikSvgIcon Icon="@SvgIcon.Comment"></TelerikSvgIcon> Comment
</TelerikButton>
<TelerikButton FillMode="Telerik.Blazor.ThemeConstants.Button.FillMode.Outline">
<TelerikSvgIcon Icon="@SvgIcon.Share"></TelerikSvgIcon> Share
</TelerikButton>
</div>
</CardFooter>
</TelerikCard>
}
</div>
</div>
@code {
private List<Post> Posts { get; set; } = new();
protected override async Task OnInitializedAsync()
{
await Task.Delay(5000);
LoadPosts();
}
private void LoadPosts()
{
Posts = new()
{
new Post
{
Id = Guid.NewGuid(),
UserName = "Bot Doe",
Content = "What a great day to try the Telerik UI for Blazor Skeleton. The UX feels great while data loads!",
ImageUrl = "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=1200&auto=format&fit=crop",
PostedAt = DateTimeOffset.UtcNow.AddMinutes(-35)
},
new Post
{
Id = Guid.NewGuid(),
UserName = "Link",
Content = ".NET 10 migration done — everything feels snappier.",
ImageUrl = null,
PostedAt = DateTimeOffset.UtcNow.AddHours(-2)
},
new Post
{
Id = Guid.NewGuid(),
UserName = "Ada Lovelace",
Content = "Pro tip: leverage high-level components to speed up demos.",
ImageUrl = "https://images.unsplash.com/photo-1518837695005-2083093ee35b?q=80&w=1200&auto=format&fit=crop",
PostedAt = DateTimeOffset.UtcNow.AddDays(-1)
}
};
}
private static string GetInitials(string name)
{
if (string.IsNullOrWhiteSpace(name)) return "?";
var parts = name.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 1) return parts[0].Substring(0, Math.Min(1, parts[0].Length)).ToUpperInvariant();
return (parts[0][0].ToString() + parts[^1][0].ToString()).ToUpperInvariant();
}
private sealed class Post
{
public Guid Id { get; set; }
public string UserName { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public string? ImageUrl { get; set; }
public DateTimeOffset PostedAt { get; set; }
}
}
<style>
.feed-container {
max-width: 820px;
margin: 0 auto;
padding: 0 1rem;
}
.card {
box-shadow: var(--kendo-box-shadow, 0 1px 3px rgba(0,0,0,0.08));
border: 1px solid rgba(0,0,0,0.06);
border-radius: .5rem;
}
.composer .k-textarea {
width: 100%;
}
.feed img {
max-height: 420px;
object-fit: cover;
}
.gap-2 {
gap: .5rem;
}
.gap-3 {
gap: 1rem;
}
</style>
If we run the application right now, you can see that we have a poor user experience, as it does not provide feedback that it is trying to fetch information to fill the UI, and you have to wait until the loading is finished to see something on the screen:
Let’s solve this issue by adding the Skeleton component. What you need to do is try to create a copy of the final graphical interface, but replacing each control with the Skeleton component in a suitable shape corresponding to the final component.
For example, a circular shape could be used for the profile picture, a rectangular shape for photographs, and leave the default shape for the text. The following is an example of this replacement in the header of the component CardHeader:
Component CardHeader with the final components rendered
<CardHeader>
<div class="d-flex align-items-center gap-3">
<TelerikAvatar Type="AvatarType.Text" Rounded="@ThemeConstants.Avatar.Rounded.Full" Width="48px" Height="48px">@GetInitials(post.UserName)</TelerikAvatar>
<div>
<div class="fw-semibold">@post.UserName</div>
<div class="text-muted small">@post.PostedAt.ToLocalTime().ToString("g")</div>
</div>
</div>
</CardHeader>
Component CardHeader using components TelerikSkeleton that will show the loading effect
<CardHeader>
<div class="d-flex align-items-center gap-3">
<TelerikSkeleton ShapeType="SkeletonShapeType.Circle" Width="48px" Height="48px" />
<div class="flex-grow-1 w-100">
<TelerikSkeleton ShapeType="SkeletonShapeType.Text" Width="35%" Height="18px" Class="mb-1" />
<TelerikSkeleton ShapeType="SkeletonShapeType.Text" Width="20%" Height="14px" />
</div>
</div>
</CardHeader>
In the previous code, I want you to notice that each type of element has been replaced by the appropriate shape of the Skeleton. In this specific example, I was able to reuse the same containers div to hold the Skeleton components, but if you need to, you can change them to work better for you.
Following this same logic, I am going to create and use a property called IsLoading. This property will allow me to control when to show and hide the loading sections through the creation of a conditional if. In this conditional, we can validate if the loading of the information has concluded, which, if positive, will show the components with information as follows:
@page "/feed"
<div class="feed-container">
<h3 class="mb-3">Feed</h3>
...
@if (IsLoading)
{
<div class="feed">
@for (int i = 0; i < 3; i++)
{
<TelerikCard Class="mb-4">
<CardHeader>
<div class="d-flex align-items-center gap-3">
<TelerikSkeleton ShapeType="SkeletonShapeType.Circle" Width="48px" Height="48px" />
<div class="flex-grow-1 w-100">
<TelerikSkeleton ShapeType="SkeletonShapeType.Text" Width="35%" Height="18px" Class="mb-1" />
<TelerikSkeleton ShapeType="SkeletonShapeType.Text" Width="20%" Height="14px" />
</div>
</div>
</CardHeader>
<CardBody>
<TelerikSkeleton ShapeType="SkeletonShapeType.Text" Width="100%" Height="14px" Class="mb-1" />
<TelerikSkeleton ShapeType="SkeletonShapeType.Text" Width="90%" Height="14px" Class="mb-1" />
<TelerikSkeleton ShapeType="SkeletonShapeType.Text" Width="80%" Height="14px" />
<TelerikSkeleton ShapeType="SkeletonShapeType.Rectangle" Width="100%" Height="220px" />
</CardBody>
<CardFooter>
<div class="d-flex gap-2">
<TelerikSkeleton ShapeType="SkeletonShapeType.Rectangle" Width="80px" Height="32px" />
<TelerikSkeleton ShapeType="SkeletonShapeType.Rectangle" Width="90px" Height="32px" />
</div>
</CardFooter>
</TelerikCard>
}
</div>
}
else
{
<div class="feed">
@foreach (var post in Posts)
{
<TelerikCard Class="mb-4">
<CardHeader>
<div class="d-flex align-items-center gap-3">
<TelerikAvatar Type="AvatarType.Text" Rounded="@ThemeConstants.Avatar.Rounded.Full" Width="48px" Height="48px">@GetInitials(post.UserName)</TelerikAvatar>
<div>
<div class="fw-semibold">@post.UserName</div>
<div class="text-muted small">@post.PostedAt.ToLocalTime().ToString("g")</div>
</div>
</div>
</CardHeader>
<CardBody>
<p class="mb-3">@post.Content</p>
@if (!string.IsNullOrWhiteSpace(post.ImageUrl))
{
<img src="@post.ImageUrl" alt="post image" class="img-fluid rounded" />
}
</CardBody>
<CardFooter>
<div class="d-flex gap-2">
<TelerikButton ThemeColor="primary" FillMode="Telerik.Blazor.ThemeConstants.Button.FillMode.Outline">
<TelerikSvgIcon Icon="@SvgIcon.Heart"></TelerikSvgIcon> Like
</TelerikButton>
<TelerikButton FillMode="Telerik.Blazor.ThemeConstants.Button.FillMode.Outline">
<TelerikSvgIcon Icon="@SvgIcon.Comment"></TelerikSvgIcon> Comment
</TelerikButton>
<TelerikButton FillMode="Telerik.Blazor.ThemeConstants.Button.FillMode.Outline">
<TelerikSvgIcon Icon="@SvgIcon.Share"></TelerikSvgIcon> Share
</TelerikButton>
</div>
</CardFooter>
</TelerikCard>
}
</div>
}
</div>
@code {
private bool IsLoading { get; set; } = true;
...
protected override async Task OnInitializedAsync()
{
...
IsLoading = false;
}
}
In the previous code, you can see that once the loading of information in the method OnInitializedAsync is completed, the value false is assigned to IsLoading, which causes the controls with the final information to be rendered. Also, note that when showing the interface using the Skeleton components, only three items are displayed. The result of the execution is as follows:
With this, you have been able to see how the loading of data in the graphical interface has been incredibly improved.
In this article, you have been able to learn what the Skeleton control for Blazor from Telerik is and how to use it, which is very useful for providing feedback to the user about a process that involves obtaining information to display in the graphical interface. You have seen its different configuration options, as well as an example in a Blazor app where we implemented the component.
Now it’s your turn to enhance the user experience in your applications by implementing the Skeleton component.
The whole Telerik UI for Blazor UI library is available to test in a 30-day free trial.
Héctor Pérez is a Microsoft MVP with more than 10 years of experience in software development. He is an independent consultant, working with business and government clients to achieve their goals. Additionally, he is an author of books and an instructor at El Camino Dev and Devs School.