
Maui Collectionview
- 42 installs
- 163 repo stars
- Updated July 6, 2026
- davidortinau/maui-skills
Implements CollectionView in .NET MAUI apps for data display, list and grid layouts, selection, grouping, scrolling, empty views, and templates.
About
Guides implementing CollectionView in .NET MAUI apps including list and grid layouts, selection, grouping, scrolling, empty views and templates. A developer uses it when building scrollable data-driven lists or grids in a MAUI UI.
- List and grid layouts with selection and grouping
- Empty views and item templates
Maui Collectionview by the numbers
- 42 all-time installs (skills.sh)
- Ranked #623 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/davidortinau/maui-skills --skill maui-collectionviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 42 |
|---|---|
| repo stars | ★ 163 |
| Last updated | July 6, 2026 |
| Repository | davidortinau/maui-skills ↗ |
What it does
Implements CollectionView in .NET MAUI apps for data display, list and grid layouts, selection, grouping, scrolling, empty views, and templates.
Files
CollectionView – .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 use `ViewCell`. - 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 useLinearItemsLayoutorGridItemsLayout.
Performance tips
- Use `MeasureFirstItem` for uniform item sizes — significantly faster than the default
MeasureAllItems:
<LinearItemsLayout Orientation="Vertical" ItemSizingStrategy="MeasureFirstItem" />- Always use `ObservableCollection<T>`, not
List<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. |
{
"skill_name": "maui-collectionview",
"evals": [
{
"id": 1,
"prompt": "I'm building a recipe app in .NET MAUI. I need to show a 2-column grid of recipes where each card shows the recipe photo, name, and prep time. Users should be able to tap a card to select it — I want to highlight the selected item. The project uses MVVM. Can you implement this?",
"expected_output": "CollectionView with GridItemsLayout (2 columns), DataTemplate with photo/name/prep time, SelectionMode=Single, visual selection feedback, ViewModel with ObservableCollection and SelectedItem property, compiled bindings with x:DataType",
"files": [],
"expectations": [
"CollectionView uses a 2-column GridItemsLayout (Span=2 or Columns=2)",
"DataTemplate includes an Image element bound to a photo or image source property",
"DataTemplate includes a Label bound to recipe Name and a Label bound to prep time",
"CollectionView has SelectionMode set to Single",
"ViewModel has a SelectedRecipe or SelectedItem property of the Recipe type with INotifyPropertyChanged",
"x:DataType compiled bindings are used on the page and/or DataTemplate",
"ViewModel uses ObservableCollection<Recipe> for the items source"
]
},
{
"id": 2,
"prompt": "My .NET MAUI e-commerce app needs a product catalog. Products should be grouped by category — each group needs a header showing the category name and item count. Individual items show product name, price formatted as currency, and an 'Add to Cart' button that fires a command. I also need pull-to-refresh to reload the catalog and an empty state message when there are no results.",
"expected_output": "CollectionView with IsGrouped=true, GroupHeaderTemplate, DataTemplate for items, PullToRefresh behavior or RefreshView wrapper, EmptyView, commands for Add to Cart, grouped ObservableCollection in ViewModel",
"files": [],
"expectations": [
"CollectionView has IsGrouped='True'",
"GroupHeaderTemplate shows category name and item count",
"Price is formatted as currency (StringFormat with C, or similar currency formatting)",
"An 'Add to Cart' button has a Command binding",
"Pull-to-refresh is implemented via RefreshView or IsBusy pattern",
"CollectionView has an EmptyView defined for the no-results state",
"ViewModel uses a grouped collection structure (ObservableCollection of group objects)"
]
},
{
"id": 3,
"prompt": "I have a MAUI chat app where new messages arrive via an ObservableCollection. I need a CollectionView that displays messages and automatically scrolls to the bottom when a new message is added. Messages from me should appear on the right, messages from others on the left. How do I implement this?",
"expected_output": "CollectionView with message DataTemplate, alignment based on sender (relative binding or converter), ScrollTo programmatic call when collection changes, CollectionView reference capture, ViewModel watching for collection changes",
"files": [],
"expectations": [
"CollectionView displays messages from an ObservableCollection",
"Message alignment differs by sender — right-aligned for current user, left-aligned for others",
"Code calls CollectionView.ScrollTo() to scroll to the last message when new messages arrive",
"A CollectionChanged or property-change handler triggers the scroll behavior",
"Message model has properties for content/text, a sender or IsFromMe flag, and optionally timestamp"
]
}
]
}
CollectionView API Reference
Layouts
Set ItemsLayout to control arrangement. Default is VerticalList.
| Layout | XAML value |
|---|---|
| Vertical list | VerticalList (default) |
| Horizontal list | HorizontalList |
| Vertical grid | GridItemsLayout with Orientation="Vertical" |
| Horizontal grid | GridItemsLayout with Orientation="Horizontal" |
Grid layout
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2"
VerticalItemSpacing="8"
HorizontalItemSpacing="8" />
</CollectionView.ItemsLayout>
</CollectionView>Horizontal list
<CollectionView ItemsSource="{Binding Items}"
ItemsLayout="HorizontalList" />ItemSizingStrategy
Controls how items are measured. Set on ItemsLayout.
| Value | Behavior |
|---|---|
MeasureAllItems | Measures every item individually (default). Accurate but slower for heterogeneous sizes. |
MeasureFirstItem | Measures only the first item and applies that size to all. Much faster for uniform items. |
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSizingStrategy="MeasureFirstItem" />
</CollectionView.ItemsLayout>ItemSpacing
Use ItemSpacing on LinearItemsLayout or VerticalItemSpacing / HorizontalItemSpacing on GridItemsLayout:
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="8" />
</CollectionView.ItemsLayout>Headers and Footers
Supports string, view, or templated:
<!-- Simple string -->
<CollectionView Header="My Items" Footer="End of list" />
<!-- Custom view -->
<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/footers are data-bound.
Selection
Selection mode
| Mode | Property to bind | Binding mode |
|---|---|---|
None | — | — |
Single | SelectedItem | TwoWay |
Multiple | SelectedItems | OneWay |
<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
1. 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;
}
}2. Bind to ObservableCollection<AnimalGroup> and set 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.GroupFooterTemplate>
<DataTemplate x:DataType="models:AnimalGroup">
<Label Text="{Binding Count, StringFormat='{0} items'}"
FontAttributes="Italic"
Padding="4,0" />
</DataTemplate>
</CollectionView.GroupFooterTemplate>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Animal">
<Label Text="{Binding Name}" Padding="16,4" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>EmptyView
Shown when ItemsSource is empty or null.
<!-- Simple string -->
<CollectionView EmptyView="No items found." />
<!-- Custom view — wrap in a 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>You can also use EmptyViewTemplate with a DataTemplateSelector to swap empty views based on state.
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 |
|---|---|
MakeVisible | Scrolls just enough to make the item visible |
Start | Scrolls item to the start of the viewport |
Center | Scrolls item to the center of the viewport |
End | Scrolls item to the end of the viewport |
Snap points
Control snap behavior after scrolling:
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
SnapPointsType="MandatorySingle"
SnapPointsAlignment="Center" />
</CollectionView.ItemsLayout>SnapPointsType:None,Mandatory,MandatorySingleSnapPointsAlignment:Start,Center,End