About maui-collectionview
A .NET MAUI skill covering CollectionView patterns for scrollable data lists and grids. It focuses on bindings, refresh and incremental loading, along with layout and threading practices that avoid common UI and performance problems.
Key features
- 01Bind items with ObservableCollection
- 02Configure item templates without ViewCell
- 03Use SwipeView with ancestor bindings
- 04Add pull-to-refresh and incremental loading
- 05Choose layouts for virtualization
- 06Improve sizing and UI-thread updates
Use cases
- 01Create a scrollable list or grid of changing data
- 02Add swipe commands and pull-to-refresh
- 03Load additional items as a list is scrolled
- 04Diagnose blank items or slow scrolling
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-collectionviewAfter installing, type/maui-collectionviewin Claude Code to confirm it loaded.
Bundled filesevals/evals.jsonreferences/collectionview-api.md
Details
From the project READMECollectionView – .NET MAUI
Use CollectionView for displaying scrollable lists and grids of data.
It replaces ListView and offers better performance, flexible layouts, and no ViewCell requirement.
Essential patterns
Basic setup
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<HorizontalStackLayout Padding="8" Spacing="8">
<Image Source="{Binding Icon}" WidthRequest="40" HeightRequest="40" />
<Label Text="{Binding Name}" VerticalOptions="Center" />
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
- Bind
ItemsSourceto anObservableCollection<T>so the UI updates on add/remove. - Each item template root must be a
LayoutorView— never useViewCell. - Always set
x:DataTypeonDataTemplatefor compiled bindings.
SwipeView — binding from inside DataTemplate
Commands inside a DataTemplate can't directly reach your ViewModel. Use RelativeSource AncestorType:
<SwipeItem Text="Delete"
BackgroundColor="Red"
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:MainViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding}" />
Pull-to-refresh
Wrap CollectionView in a RefreshView. Set IsRefreshing back to false when done:
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CollectionView ItemsSource="{Binding Items}" />
</RefreshView>
Incremental loading (infinite scroll)
<CollectionView ItemsSource="{Binding Items}"
RemainingItemsThreshold="5"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}" />
⚠️ Do NOT use with StackLayout-based ItemsLayout — it has no virtualization and triggers infinite threshold-reached events. Always use
LinearItemsLayoutorGridItemsLayout.
Performance tips
- Use
MeasureFirstItemfor uniform item sizes — significantly faster than the defaultMeasureAllItems:<LinearItemsLayout Orientation="Vertical" ItemSizingStrategy="MeasureFirstItem" /> - Always use
ObservableCollection<T>, notList<T>. Swapping aListforces a full re-render. - Update collections on the UI thread —
MainThread.BeginInvokeOnMainThread(() => Items.Add(item)).
Common gotchas
| Issue | Fix |
|---|---|
| UI doesn't update when items change | Use ObservableCollection<T>, not List<T>. |
| App crashes or blank items | Never use ViewCell — use Grid, StackLayout, or any View as template root. |
| Items disappear or layout breaks | Always update ItemsSource and the collection on the UI thread (MainThread.BeginInvokeOnMainThread). |
| Incremental loading fires endlessly | Don't use StackLayout as layout; use LinearItemsLayout or GridItemsLayout. |
| EmptyView doesn't render correctly | Wrap custom empty views in ContentView. |
| Poor scroll performance | Use MeasureFirstItem sizing strategy for uniform item sizes. |
| Selected state not visible | Add VisualState Name="Selected" to the item template root element. |
| Binding errors in SwipeView commands | Use RelativeSource AncestorType to reach the ViewModel from inside the item template. |