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.
Frameworks
.NET MAUI
Blazor
Features
Section titled “Features”- Virtualization — Native recycling on Android (
RecyclerView+GridLayoutManager), compositional layout on iOS (UICollectionViewCompositionalLayout), andItemsRepeater+UniformGridLayouton Windows. Blazor uses the built-in<Virtualize>component with CSS Grid. - Sticky Group Headers — When
IsGroupingEnabledistrue, group headers pin to the top of the viewport as the user scrolls through a group. - Orientation-Aware Columns — Set
PortraitColumnCountandLandscapeColumnCountindependently so the layout adapts automatically on device rotation. - Load-More Pagination — A
LoadMoreThresholdtriggersLoadMoreRequestedbefore the user reaches the last item; an optionalShowLoadMoreButtonrenders an explicit button for manual triggering. - Item Visibility Events —
ItemVisibleCommandandItemHiddenCommandfire as items enter and leave the viewport (MAUI). - Flexible Templates — Header, footer, empty view, group header, and load-more button are all fully templatable.
AI Skill
Section titled “AI Skill”Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skills Step 2 — Install plugins:
claude plugin install shiny-client@shiny claude plugin install shiny-maui@shiny claude plugin install controls@shiny claude plugin install shiny-mediator@shiny claude plugin install shiny-data@shiny claude plugin install shiny-aspire@shiny claude plugin install shiny-extensions@shiny Step 1 — Add the marketplace:
copilot plugin marketplace add https://github.com/shinyorg/skills Step 2 — Install plugins:
copilot plugin install shiny-client@shiny copilot plugin install shiny-maui@shiny copilot plugin install controls@shiny copilot plugin install shiny-mediator@shiny copilot plugin install shiny-data@shiny copilot plugin install shiny-aspire@shiny copilot plugin install shiny-extensions@shiny Quick Start
Section titled “Quick Start”.NET MAUI
Section titled “.NET MAUI”<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>Blazor
Section titled “Blazor”<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) { }}Properties
Section titled “Properties”| Property | Type | Default | Description |
|---|---|---|---|
| ItemsSource | IEnumerable | — | Collection of items or grouped collections |
| ItemTemplate | DataTemplate | — | Template for each grid cell |
| ColumnCount | int | 1 | Default column count |
| PortraitColumnCount | int? | null | Column count in portrait orientation; overrides ColumnCount |
| LandscapeColumnCount | int? | null | Column count in landscape orientation; overrides ColumnCount |
| CellPadding | Thickness | — | Padding applied inside each cell |
| IsGroupingEnabled | bool | false | Enable grouped data with group headers |
| GroupHeaderTemplate | DataTemplate | — | Template for group header rows |
| HasStickyHeaders | bool | true | Pin group headers to the top while scrolling |
| ShowLoadMoreButton | bool | false | Render an explicit load-more button as a footer at the end of the data |
| LoadMoreButtonTemplate | DataTemplate | — | Custom template for the load-more button; defaults to a centered “Load More” button |
| IsLoadingMore | bool | false | Indicates a load-more operation is in progress |
| ItemSelectedCommand | ICommand | — | Command fired when a cell is tapped |
| ItemVisibleCommand | ICommand | — | Command fired when a cell enters the viewport |
| ItemHiddenCommand | ICommand | — | Command fired when a cell leaves the viewport |
Blazor
Section titled “Blazor”| Property | Type | Default | Description |
|---|---|---|---|
| Items | IReadOnlyList<TItem> | — | Flat list of items |
| ItemTemplate | RenderFragment<TItem> | — | Template for each grid cell |
| HeaderTemplate | RenderFragment | — | Content rendered above the grid |
| FooterTemplate | RenderFragment | — | Content rendered below the grid |
| EmptyViewTemplate | RenderFragment | — | Content rendered when Items is empty |
| ColumnCount | int | 1 | Number of grid columns |
| ItemSpacing | double | 8 | Gap between cells in px |
| CellPaddingLeft | double | — | Left padding inside each cell |
| CellPaddingRight | double | — | Right padding inside each cell |
| CellPaddingTop | double | — | Top padding inside each cell |
| CellPaddingBottom | double | — | Bottom padding inside each cell |
| IsGroupingEnabled | bool | false | Enable grouped rendering |
| GroupedItems | IReadOnlyList<VirtualizedGridGroup<TItem>> | — | Pre-grouped data source used when IsGroupingEnabled is true |
| GroupHeaderTemplate | RenderFragment<object> | — | Template for group header rows |
| HasStickyHeaders | bool | true | Pin group headers while scrolling |
| EnableVirtualization | bool | false | Use Blazor <Virtualize> for large datasets |
| LoadMoreThreshold | int | 5 | Number of items from the end that triggers LoadMoreRequested |
| ShowLoadMoreButton | bool | false | Render an explicit load-more button |
| IsLoadingMore | bool | false | Indicates a load-more operation is in progress |
| LoadMoreRequested | EventCallback | — | Fired when the threshold is reached or the load-more button is clicked |
| ItemSelected | EventCallback<TItem> | — | Fired when a cell is clicked/tapped |
Events & Commands
Section titled “Events & Commands”MAUI:
ItemSelectedCommand(ICommand) — Fires on cell tap; parameter is the selected itemItemVisibleCommand(ICommand) — Fires as a cell scrolls into the visible areaItemHiddenCommand(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 pressedItemSelected(EventCallback<TItem>) — Fires on cell click/tap
Behavior
Section titled “Behavior”- When both
PortraitColumnCountandLandscapeColumnCountare set,ColumnCountis ignored and the appropriate value is chosen based on current device orientation - Sticky headers require
IsGroupingEnabled="true"; settingHasStickyHeaders="false"renders standard (non-sticky) group headers LoadMoreThresholdcounts from the last item; reaching that position firesLoadMoreRequestedautomatically even withoutShowLoadMoreButton- Setting
IsLoadingMore="true"shows a built-in activity indicator in the load-more area until the value returns tofalse EnableVirtualizationin Blazor requiresItemsto be provided as a flat list; useGroupedItemsalongsideIsGroupingEnabledfor grouped virtualized rendering