Blazor Built-in Components Created: 04 Feb 2026 Updated: 04 Feb 2026

Component Virtualization in ASP.NET Core Blazor

Introduction

Component virtualization is a technique for limiting UI rendering to only the visible parts of a long list. This dramatically improves performance when rendering large data sets.

Without virtualization, rendering 10,000 items would create 10,000 DOM elements. With virtualization, only the visible items (typically 10-20) are rendered, plus a small buffer.

When to Use Virtualization

Scenario Recommendation
< 100 itemsRegular foreach is fine
100 - 1,000 itemsConsider virtualization
> 1,000 itemsStrongly recommended

The Virtualize Component

The Virtualize<TItem> component is a built-in Blazor component that efficiently renders lists by only creating DOM elements for visible items.

Important: The Virtualize component requires an interactive render mode to function. It will not work with static server-side rendering (SSR).

Render Mode Requirement

The Virtualize component needs interactivity to handle scroll events. Add one of the following render modes to your page:

@page "/users"
@rendermode InteractiveServer

Or for WebAssembly:

@page "/users"
@rendermode InteractiveWebAssembly
Render Mode Description
InteractiveServerServer-side interactivity via SignalR
InteractiveWebAssemblyClient-side interactivity via WebAssembly
InteractiveAutoStarts with Server, then switches to WebAssembly

Basic Usage

Using Items Property

The simplest way to use virtualization with an in-memory collection:

@page "/users"
@rendermode InteractiveServer

<div style="height: 400px; overflow-y: auto;">
<Virtualize Items="users" Context="user">
<div class="user-item">
<strong>@user.Name</strong> - @user.Email
</div>
</Virtualize>
</div>

@code {
private List<User> users = [];

protected override void OnInitialized()
{
users = Enumerable.Range(1, 10000)
.Select(i => new User { Name = $"User {i}", Email = $"user{i}@example.com" })
.ToList();
}

private record User
{
public string Name { get; init; } = string.Empty;
public string Email { get; init; } = string.Empty;
}
}
Important: The container must have a fixed height and overflow-y: auto for virtualization to work.

ItemsProvider for Async Loading

For large datasets, use ItemsProvider to load items on-demand:

<Virtualize ItemsProvider="LoadItemsAsync" Context="item">
<div>@item.Name</div>
</Virtualize>

@code {
private async ValueTask<ItemsProviderResult<Item>> LoadItemsAsync(
ItemsProviderRequest request)
{
// request.StartIndex - First item index to load
// request.Count - Number of items to load
// request.CancellationToken - Cancellation token

var items = await DataService.GetItemsAsync(
request.StartIndex,
request.Count,
request.CancellationToken);

var totalCount = await DataService.GetTotalCountAsync();

return new ItemsProviderResult<Item>(items, totalCount);
}
}

ItemsProviderRequest Properties

PropertyTypeDescription
StartIndexintIndex of the first item to load
CountintNumber of items requested
CancellationTokenCancellationTokenCancellation token for the request

ItemsProviderResult Constructor

new ItemsProviderResult<T>(IEnumerable<T> items, int totalItemCount)

Placeholder Template

Show a loading indicator while items are being fetched:

<Virtualize ItemsProvider="LoadItemsAsync" Context="item">
<ItemContent>
<div class="item">
<strong>@item.Name</strong>
<span>@item.Description</span>
</div>
</ItemContent>
<Placeholder>
<div class="item placeholder-glow">
<span class="placeholder col-6"></span>
<span class="placeholder col-4"></span>
</div>
</Placeholder>
</Virtualize>

Empty Content Template

Display a message when the collection is empty:

<Virtualize Items="items" Context="item">
<ItemContent>
<div>@item.Name</div>
</ItemContent>
<EmptyContent>
<div class="alert alert-info">
No items found. Try adjusting your search criteria.
</div>
</EmptyContent>
</Virtualize>

Configuring Item Size

The ItemSize parameter specifies the approximate height (in pixels) of each item:

<Virtualize Items="items" Context="item" ItemSize="80">
<div style="height: 80px;">
@item.Name
</div>
</Virtualize>
PropertyDefaultDescription
ItemSize50Height of each item in pixels
Tip: Set ItemSize to match your actual item height for smoother scrolling.

Configuring Overscan

The OverscanCount parameter specifies how many extra items to render above and below the visible area:

<Virtualize Items="items" Context="item" OverscanCount="10">
<div>@item.Name</div>
</Virtualize>
Property Default Description
OverscanCount3Number of extra items to render outside the viewport

Higher values:

  1. ✅ Smoother scrolling experience
  2. ❌ More memory usage

Lower values:

  1. ✅ Less memory usage
  2. ❌ May see brief flashes during fast scrolling

SpacerElement Customization

Customize the spacer element used for scroll calculations:

<Virtualize Items="items" Context="item" SpacerElement="tr">
<tr>
<td>@item.Name</td>
<td>@item.Value</td>
</tr>
</Virtualize>

Useful for HTML tables where spacers need to be <tr> elements.

Refreshing Data

Call RefreshDataAsync() to reload the data:

<Virtualize @ref="virtualizeComponent" Items="items" Context="item">
<div>@item.Name</div>
</Virtualize>

<button @onclick="RefreshAsync">Refresh</button>

@code {
private Virtualize<Item>? virtualizeComponent;
private List<Item> items = [];

private async Task RefreshAsync()
{
items = await LoadNewDataAsync();
if (virtualizeComponent is not null)
{
await virtualizeComponent.RefreshDataAsync();
}
}
}

Complete Example with All Features

@page "/products"
@rendermode InteractiveServer
@inject IProductService ProductService

<PageTitle>Products</PageTitle>

<div class="search-box mb-3">
<input @bind="searchTerm" @bind:event="oninput"
placeholder="Search products..."
class="form-control" />
</div>

<div style="height: 500px; overflow-y: auto;">
<Virtualize @ref="virtualize"
ItemsProvider="LoadProductsAsync"
Context="product"
ItemSize="70"
OverscanCount="5">
<ItemContent>
<div class="product-card d-flex justify-content-between p-3 border-bottom">
<div>
<h5>@product.Name</h5>
<small class="text-muted">@product.Category</small>
</div>
<div class="text-end">
<span class="badge bg-primary">$@product.Price</span>
</div>
</div>
</ItemContent>
<Placeholder>
<div class="product-card d-flex justify-content-between p-3 border-bottom">
<div class="placeholder-glow" style="width: 60%;">
<span class="placeholder col-8"></span>
<span class="placeholder col-4"></span>
</div>
<span class="placeholder col-2"></span>
</div>
</Placeholder>
<EmptyContent>
<div class="alert alert-warning text-center">
No products found matching "@searchTerm"
</div>
</EmptyContent>
</Virtualize>
</div>

@code {
private Virtualize<Product>? virtualize;
private string searchTerm = string.Empty;

private async ValueTask<ItemsProviderResult<Product>> LoadProductsAsync(
ItemsProviderRequest request)
{
var result = await ProductService.GetProductsAsync(
searchTerm,
request.StartIndex,
request.Count,
request.CancellationToken);

return new ItemsProviderResult<Product>(result.Items, result.TotalCount);
}

private async Task OnSearchChanged()
{
if (virtualize is not null)
{
await virtualize.RefreshDataAsync();
}
}
}

Performance Comparison

Without Virtualization (10,000 items)

@foreach (var item in items)
{
<div>@item.Name</div>
}
  1. ❌ Creates 10,000 DOM elements
  2. ❌ Slow initial render (seconds)
  3. ❌ High memory usage
  4. ❌ Sluggish scrolling

With Virtualization (10,000 items)

<Virtualize Items="items" Context="item">
<div>@item.Name</div>
</Virtualize>
  1. ✅ Creates ~20-30 DOM elements
  2. ✅ Fast initial render (milliseconds)
  3. ✅ Low memory usage
  4. ✅ Smooth scrolling

Common Issues and Solutions

Issue Cause Solution
List is empty/not renderingMissing render modeAdd @rendermode InteractiveServer or InteractiveWebAssembly
Items don't scrollContainer has no heightAdd height and overflow-y: auto to container
Blank spaces appearItemSize doesn't match actualSet ItemSize to match your item height
Flashing during scrollOverscanCount too lowIncrease OverscanCount
Items reload constantlyMissing CancellationTokenUse request.CancellationToken in async calls

Summary

Parameter Type Default Description
ItemsICollection<TItem>-In-memory collection
ItemsProviderItemsProviderDelegate<TItem>-Async data provider
ItemContentRenderFragment<TItem>-Template for each item
PlaceholderRenderFragment<PlaceholderContext>-Loading template
EmptyContentRenderFragment-Empty state template
ItemSizefloat50Item height in pixels
OverscanCountint3Extra items to render
SpacerElementstring"div"Spacer element type
Share this lesson: