Skills maui-collectionview
install
source · Clone the upstream repo
git clone https://github.com/dotnet/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/dotnet/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/dotnet-maui/skills/maui-collectionview" ~/.claude/skills/dotnet-skills-maui-collectionview && rm -rf "$T"
manifest:
plugins/dotnet-maui/skills/maui-collectionview/SKILL.mdsource content
CollectionView — .NET MAUI
CollectionView is the primary control for displaying scrollable lists and grids of data in .NET MAUI. It replaces ListView with better performance, flexible layouts, and no ViewCell requirement.
When to Use
- Displaying a scrollable list or grid of data items
- Binding a collection of objects to a templated item layout
- Adding selection (single or multiple), grouping, or pull-to-refresh
- Implementing infinite scroll / incremental loading
- Showing swipe actions on list items
- Displaying an empty state when no data is available
When Not to Use
- Static layouts with a fixed number of items — use
orGrid
directlyStackLayout - Map pin lists — use the
NuGet packageMicrosoft.Maui.Controls.Maps - Table-based data entry forms — use standard form controls
- Simple text-only lists with no interaction — consider
on aBindableLayoutStackLayout
Inputs
- A data source (typically
) bound toObservableCollection<T>ItemsSource - A
defining how each item rendersDataTemplate - Optional: layout configuration, selection mode, grouping model, empty view
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>
Key rules:
- Bind
to anItemsSource
so the UI updates on add/remove.ObservableCollection<T> - Each item template root must be a
orLayout
— never useView
.ViewCell - Always set
onx:DataType
for compiled bindings.DataTemplate
Layouts
Set
ItemsLayout to control arrangement. Default is VerticalList.
| Layout | XAML value |
|---|---|
| Vertical list | (default) |
| Horizontal list | |
| Vertical grid | with |
| Horizontal grid | with |
Grid Layout
<CollectionView ItemsSource="{Binding Items}"> <CollectionView.ItemsLayout> <GridItemsLayout Orientation="Vertical" Span="2" VerticalItemSpacing="8" HorizontalItemSpacing="8" /> </CollectionView.ItemsLayout> <CollectionView.ItemTemplate> <DataTemplate x:DataType="models:Item"> <Border Padding="8" StrokeThickness="0"> <VerticalStackLayout> <Image Source="{Binding Image}" HeightRequest="120" Aspect="AspectFill" /> <Label Text="{Binding Name}" FontAttributes="Bold" /> </VerticalStackLayout> </Border> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView>
Horizontal List
<CollectionView ItemsSource="{Binding Items}" ItemsLayout="HorizontalList" />
Selection
Selection Mode
| Mode | Property to bind | Binding mode |
|---|---|---|
| — | — |
| | |
| | |
<CollectionView ItemsSource="{Binding Items}" SelectionMode="Single" SelectedItem="{Binding CurrentItem, Mode=TwoWay}" SelectionChangedCommand="{Binding ItemSelectedCommand}" />
For
Multiple selection, bind SelectedItems (type IList<object>):
<CollectionView SelectionMode="Multiple" SelectedItems="{Binding ChosenItems, Mode=OneWay}" />
Selected Visual State
Highlight selected items using
VisualStateManager:
<CollectionView.ItemTemplate> <DataTemplate x:DataType="models:Item"> <Grid Padding="8"> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="CommonStates"> <VisualState Name="Normal"> <VisualState.Setters> <Setter Property="BackgroundColor" Value="Transparent" /> </VisualState.Setters> </VisualState> <VisualState Name="Selected"> <VisualState.Setters> <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Label Text="{Binding Name}" /> </Grid> </DataTemplate> </CollectionView.ItemTemplate>
Grouping
- Create a group class inheriting from
:List<T>
public class AnimalGroup : List<Animal> { public string Name { get; } public AnimalGroup(string name, List<Animal> animals) : base(animals) { Name = name; } }
- Bind to
and setObservableCollection<AnimalGroup>
:IsGrouped="True"
<CollectionView ItemsSource="{Binding AnimalGroups}" IsGrouped="True"> <CollectionView.GroupHeaderTemplate> <DataTemplate x:DataType="models:AnimalGroup"> <Label Text="{Binding Name}" FontAttributes="Bold" BackgroundColor="{StaticResource Gray100}" Padding="8" /> </DataTemplate> </CollectionView.GroupHeaderTemplate> <CollectionView.ItemTemplate> <DataTemplate x:DataType="models:Animal"> <Label Text="{Binding Name}" Padding="16,4" /> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView>
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 non-virtualizing layouts.
andLinearItemsLayoutsupport virtualization. UsingGridItemsLayouton aBindableLayoutas an alternative toStackLayouthas no virtualization, which triggers infinite threshold-reached events.CollectionView
SwipeView — Binding from Inside DataTemplate
Commands inside a
DataTemplate can't directly reach your ViewModel. Use RelativeSource AncestorType:
<CollectionView.ItemTemplate> <DataTemplate x:DataType="models:Item"> <SwipeView> <SwipeView.RightItems> <SwipeItems> <SwipeItem Text="Delete" BackgroundColor="Red" Command="{Binding BindingContext.DeleteCommand, Source={RelativeSource AncestorType={x:Type ContentPage}}}" CommandParameter="{Binding}" /> </SwipeItems> </SwipeView.RightItems> <Grid Padding="8"> <Label Text="{Binding Name}" /> </Grid> </SwipeView> </DataTemplate> </CollectionView.ItemTemplate>
EmptyView
Shown when
ItemsSource is empty or null.
<CollectionView ItemsSource="{Binding SearchResults}" EmptyView="No items found." />
For a custom empty view, wrap in
ContentView:
<CollectionView ItemsSource="{Binding SearchResults}"> <CollectionView.EmptyView> <ContentView> <VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center"> <Image Source="empty_state.png" WidthRequest="120" /> <Label Text="Nothing here yet" HorizontalTextAlignment="Center" /> </VerticalStackLayout> </ContentView> </CollectionView.EmptyView> </CollectionView>
Headers and Footers
<CollectionView ItemsSource="{Binding Items}"> <CollectionView.Header> <Label Text="Header" FontAttributes="Bold" Padding="8" /> </CollectionView.Header> <CollectionView.Footer> <Label Text="Footer" FontAttributes="Italic" Padding="8" /> </CollectionView.Footer> </CollectionView>
Use
HeaderTemplate / FooterTemplate when headers or footers are data-bound.
Scrolling
ScrollTo
Programmatically scroll by index or item:
// Scroll to index collectionView.ScrollTo(index: 10, position: ScrollToPosition.Center, animate: true); // Scroll to item collectionView.ScrollTo(item: myItem, position: ScrollToPosition.MakeVisible, animate: true);
| ScrollToPosition | Behavior |
|---|---|
| Scrolls just enough to make the item visible |
| Scrolls item to the start of the viewport |
| Scrolls item to the center of the viewport |
| Scrolls item to the end of the viewport |
Snap Points
<CollectionView.ItemsLayout> <LinearItemsLayout Orientation="Horizontal" SnapPointsType="MandatorySingle" SnapPointsAlignment="Center" /> </CollectionView.ItemsLayout>
:SnapPointsType
,None
,MandatoryMandatorySingle
:SnapPointsAlignment
,Start
,CenterEnd
Performance Tips
- Use
for uniform item sizes — significantly faster thanMeasureFirstItem
:MeasureAllItems<LinearItemsLayout Orientation="Vertical" ItemSizingStrategy="MeasureFirstItem" /> - Always use
, notObservableCollection<T>
. Swapping aList<T>
forces a full re-render.List - Update collections on the UI thread —
.MainThread.BeginInvokeOnMainThread(() => Items.Add(item))
Common Pitfalls
| Issue | Fix |
|---|---|
| UI doesn't update when items change | Use , not . |
| App crashes or blank items | Never use — use , , or any as template root. |
| Items disappear or layout breaks | Always update and the collection on the UI thread (). |
| Incremental loading fires endlessly | Don't use as layout; use or . |
| EmptyView doesn't render correctly | Wrap custom empty views in . |
| Poor scroll performance | Use sizing strategy for uniform item sizes. |
| Selected state not visible | Add to the item template root element. |
| Binding errors in SwipeView commands | Use to reach the ViewModel from inside the item template. |
| Using ListView instead of CollectionView | replaces — it has better performance, no , and flexible layouts. |