Skip to content
Introducing AI Conversations: Natural Language Interaction for Your Apps! Learn More

VirtualizedGrid

A high-performance virtualized grid that supports optional grouping with sticky headers, orientation-aware column counts, and built-in load-more pagination. Only the items currently visible in the viewport are created and measured, keeping memory use flat regardless of data set size.

  • NuGet downloads for Shiny.Maui.Controls
  • NuGet downloads for Shiny.Blazor.Controls
Frameworks
.NET MAUI
Blazor
  • Virtualization — Native recycling on Android (RecyclerView + GridLayoutManager), compositional layout on iOS (UICollectionViewCompositionalLayout), and ItemsRepeater + UniformGridLayout on Windows. Blazor uses the built-in <Virtualize> component with CSS Grid.
  • Sticky Group Headers — When IsGroupingEnabled is true, group headers pin to the top of the viewport as the user scrolls through a group.
  • Orientation-Aware Columns — Set PortraitColumnCount and LandscapeColumnCount independently so the layout adapts automatically on device rotation.
  • Load-More Pagination — A LoadMoreThreshold triggers LoadMoreRequested before the user reaches the last item; an optional ShowLoadMoreButton renders an explicit button for manual triggering.
  • Item Visibility EventsItemVisibleCommand and ItemHiddenCommand fire as items enter and leave the viewport (MAUI).
  • Flexible Templates — Header, footer, empty view, group header, and load-more button are all fully templatable.
claude plugin marketplace add shinyorg/skills
claude plugin install shiny-client@shiny
BLE, GPS, Jobs, Notifications, Push, HTTP Transfers, OBD, Music, Health, DataSync — iOS, Android, Windows, MacOS, Linux, Web
claude plugin install shiny-maui@shiny
Shell, Contact Store
claude plugin install controls@shiny
TableView, BottomSheet, PillView, ImageViewer, Scheduler, Markdown, Mermaid Diagrams — MAUI and Blazor
claude plugin install shiny-mediator@shiny
Mediator/CQRS with middleware and source generators
claude plugin install shiny-data@shiny
DocumentDB and Spatial data libraries
claude plugin install shiny-aspire@shiny
Orleans and Gluetun Aspire integrations
claude plugin install shiny-extensions@shiny
DI, Stores, Reflector, Localization, Hosting modules
copilot plugin marketplace add https://github.com/shinyorg/skills
copilot plugin install shiny-client@shiny
BLE, GPS, Jobs, Notifications, Push, HTTP Transfers, OBD, Music, Health, DataSync — iOS, Android, Windows, MacOS, Linux, Web
copilot plugin install shiny-maui@shiny
Shell, Contact Store
copilot plugin install controls@shiny
TableView, BottomSheet, PillView, ImageViewer, Scheduler, Markdown, Mermaid Diagrams — MAUI and Blazor
copilot plugin install shiny-mediator@shiny
Mediator/CQRS with middleware and source generators
copilot plugin install shiny-data@shiny
DocumentDB and Spatial data libraries
copilot plugin install shiny-aspire@shiny
Orleans and Gluetun Aspire integrations
copilot plugin install shiny-extensions@shiny
DI, Stores, Reflector, Localization, Hosting modules
View Skills Repository
<shiny:VirtualizedGrid ItemsSource="{Binding Products}"
PortraitColumnCount="2"
LandscapeColumnCount="4"
IsGroupingEnabled="True"
HasStickyHeaders="True"
CellPadding="8"
ShowLoadMoreButton="False"
IsLoadingMore="{Binding IsLoadingMore}"
ItemSelectedCommand="{Binding SelectProductCommand}">
<shiny:VirtualizedGrid.ItemTemplate>
<DataTemplate>
<Border StrokeShape="{RoundRectangle CornerRadius=8}" Padding="8">
<Label Text="{Binding Name}" />
</Border>
</DataTemplate>
</shiny:VirtualizedGrid.ItemTemplate>
<shiny:VirtualizedGrid.GroupHeaderTemplate>
<DataTemplate>
<Label Text="{Binding Key}" FontAttributes="Bold" FontSize="16" />
</DataTemplate>
</shiny:VirtualizedGrid.GroupHeaderTemplate>
</shiny:VirtualizedGrid>
<VirtualizedGrid Items="products"
ColumnCount="2"
ItemSpacing="8"
IsGroupingEnabled="true"
HasStickyHeaders="true"
EnableVirtualization="true"
LoadMoreThreshold="5"
IsLoadingMore="isLoadingMore"
LoadMoreRequested="OnLoadMore"
ItemSelected="OnProductSelected">
<ItemTemplate Context="product">
<div class="product-card">
<span>@product.Name</span>
</div>
</ItemTemplate>
<GroupHeaderTemplate Context="group">
<h3>@group</h3>
</GroupHeaderTemplate>
<EmptyViewTemplate>
<p>No products found.</p>
</EmptyViewTemplate>
</VirtualizedGrid>
@code {
List<Product> products = [];
bool isLoadingMore = false;
async Task OnLoadMore()
{
isLoadingMore = true;
// fetch next page...
isLoadingMore = false;
}
void OnProductSelected(Product product) { }
}
PropertyTypeDefaultDescription
ItemsSourceIEnumerableCollection of items or grouped collections
ItemTemplateDataTemplateTemplate for each grid cell
ColumnCountint1Default column count
PortraitColumnCountint?nullColumn count in portrait orientation; overrides ColumnCount
LandscapeColumnCountint?nullColumn count in landscape orientation; overrides ColumnCount
CellPaddingThicknessPadding applied inside each cell
IsGroupingEnabledboolfalseEnable grouped data with group headers
GroupHeaderTemplateDataTemplateTemplate for group header rows
HasStickyHeadersbooltruePin group headers to the top while scrolling
ShowLoadMoreButtonboolfalseRender an explicit load-more button as a footer at the end of the data
LoadMoreButtonTemplateDataTemplateCustom template for the load-more button; defaults to a centered “Load More” button
IsLoadingMoreboolfalseIndicates a load-more operation is in progress
ItemSelectedCommandICommandCommand fired when a cell is tapped
ItemVisibleCommandICommandCommand fired when a cell enters the viewport
ItemHiddenCommandICommandCommand fired when a cell leaves the viewport
PropertyTypeDefaultDescription
ItemsIReadOnlyList<TItem>Flat list of items
ItemTemplateRenderFragment<TItem>Template for each grid cell
HeaderTemplateRenderFragmentContent rendered above the grid
FooterTemplateRenderFragmentContent rendered below the grid
EmptyViewTemplateRenderFragmentContent rendered when Items is empty
ColumnCountint1Number of grid columns
ItemSpacingdouble8Gap between cells in px
CellPaddingLeftdoubleLeft padding inside each cell
CellPaddingRightdoubleRight padding inside each cell
CellPaddingTopdoubleTop padding inside each cell
CellPaddingBottomdoubleBottom padding inside each cell
IsGroupingEnabledboolfalseEnable grouped rendering
GroupedItemsIReadOnlyList<VirtualizedGridGroup<TItem>>Pre-grouped data source used when IsGroupingEnabled is true
GroupHeaderTemplateRenderFragment<object>Template for group header rows
HasStickyHeadersbooltruePin group headers while scrolling
EnableVirtualizationboolfalseUse Blazor <Virtualize> for large datasets
LoadMoreThresholdint5Number of items from the end that triggers LoadMoreRequested
ShowLoadMoreButtonboolfalseRender an explicit load-more button
IsLoadingMoreboolfalseIndicates a load-more operation is in progress
LoadMoreRequestedEventCallbackFired when the threshold is reached or the load-more button is clicked
ItemSelectedEventCallback<TItem>Fired when a cell is clicked/tapped

MAUI:

  • ItemSelectedCommand (ICommand) — Fires on cell tap; parameter is the selected item
  • ItemVisibleCommand (ICommand) — Fires as a cell scrolls into the visible area
  • ItemHiddenCommand (ICommand) — Fires as a cell scrolls out of the visible area

Blazor:

  • LoadMoreRequested (EventCallback) — Fires when proximity threshold is reached or load-more button is pressed
  • ItemSelected (EventCallback<TItem>) — Fires on cell click/tap
  • When both PortraitColumnCount and LandscapeColumnCount are set, ColumnCount is ignored and the appropriate value is chosen based on current device orientation
  • Sticky headers require IsGroupingEnabled="true"; setting HasStickyHeaders="false" renders standard (non-sticky) group headers
  • LoadMoreThreshold counts from the last item; reaching that position fires LoadMoreRequested automatically even without ShowLoadMoreButton
  • Setting IsLoadingMore="true" shows a built-in activity indicator in the load-more area until the value returns to false
  • EnableVirtualization in Blazor requires Items to be provided as a flat list; use GroupedItems alongside IsGroupingEnabled for grouped virtualized rendering