Source profileQuality 91/100

dotnet/skills/plugins/dotnet-maui/skills/maui-collectionview/SKILL.md

maui-collectionview

Guidance for implementing CollectionView in .NET MAUI apps — data display, layouts (list & grid), selection, grouping, scrolling, empty views, templates, incremental loading, swipe actions, and pull-to-refresh. USE FOR: "CollectionView", "list view", "grid layout", "data template", "item template", "grouping", "pull to refresh", "incremental loading", "swipe actions", "empty view", "selection mode", "scroll to item", displaying scrollable data, replacing ListView. DO NOT USE FOR: simple static l

Source repository stars
5,248
Declared platforms
0
Static risk flags
1
Last source update
2026-08-26
Source checked
2026-08-26

Decision brief

What it does: where it fits

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.

Best for

  • 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

Not for

  • Static layouts with a fixed number of items — use Grid or StackLayout directly
  • Map pin lists — use the Microsoft.Maui.Controls.Maps NuGet package

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/dotnet/skills --skill "plugins/dotnet-maui/skills/maui-collectionview"
Safe inspection promptEditorial

Inspect the Agent Skill "maui-collectionview" from https://github.com/dotnet/skills/blob/1b896e91feb0f613cb54a914f1efd2897810ae02/plugins/dotnet-maui/skills/maui-collectionview/SKILL.md at commit 1b896e91feb0f613cb54a914f1efd2897810ae02. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

Workflow

What the source asks the agent to do

  1. 01

    Basic Setup

    A complete, copy-pasteable page. Two things are load-bearing: the xmlns:models declaration that every x:DataType="models:Item" in this skill assumes, and the root x:DataType — without it the outer ItemsSource binding is not compiled:

    Bind ItemsSource to an ObservableCollection so the UI updates on add/remove.Each item template root must be a Layout or View — never use ViewCell.Always set x:DataType on DataTemplate for compiled bindings.
  2. 02

    When to Use

    Displaying a scrollable list or grid of data items

    Displaying a scrollable list or grid of data itemsBinding a collection of objects to a templated item layoutAdding selection (single or multiple), grouping, or pull-to-refresh
  3. 03

    When Not to Use

    Static layouts with a fixed number of items — use Grid or StackLayout directly

    Static layouts with a fixed number of items — use Grid or StackLayout directlyMap pin lists — use the Microsoft.Maui.Controls.Maps NuGet packageTable-based data entry forms — use standard form controls
  4. 04

    Scope Control — Answer Only What Was Asked

    This skill is a reference you consult, not a checklist you apply. Most requests need one or two sections from it. Pulling in the rest makes the answer worse.

    The user asked a narrow question. Answer that question only. Do not appendThe user's existing code already works. Do not rewrite working markup toThe change is stylistic. Renaming, reordering attributes, or restructuring
  5. 05

    Inputs

    A data source (typically ObservableCollection) bound to ItemsSource

    A data source (typically ObservableCollection) bound to ItemsSourceA DataTemplate defining how each item rendersOptional: layout configuration, selection mode, grouping model, empty view

Permission review

Static risk signals and limitations

Network access

medium · line 71

The documentation includes network, browsing, or remote request actions.

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"

Network access

medium · line 72

The documentation includes network, browsing, or remote request actions.

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

Evidence record

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score91/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars5,248SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
dotnet/skills
Skill path
plugins/dotnet-maui/skills/maui-collectionview/SKILL.md
Commit
1b896e91feb0f613cb54a914f1efd2897810ae02
License
MIT
Collected
2026-08-26
Default branch
main
View the original SKILL.md

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 Grid or StackLayout directly
  • Map pin lists — use the Microsoft.Maui.Controls.Maps NuGet package
  • Table-based data entry forms — use standard form controls
  • Simple text-only lists with no interaction — consider BindableLayout on a StackLayout

Scope Control — Answer Only What Was Asked

This skill is a reference you consult, not a checklist you apply. Most requests need one or two sections from it. Pulling in the rest makes the answer worse.

Stop conditions — do NOT act when:

  • The user asked a narrow question. Answer that question only. Do not append grouping, swipe actions, empty views, snap points, or performance tips that were not asked about.
  • The user's existing code already works. Do not rewrite working markup to match the examples here. Point out a concrete defect; if there is none, say so and answer the question that was asked.
  • The change is stylistic. Renaming, reordering attributes, or restructuring a template that already behaves correctly is churn, not a fix.
  • The control isn't CollectionView. CarouselView, BindableLayout, and ListView-in-maintenance code have different rules. Do not rewrite ListView code the user did not ask about — but if they ask which control to use, or are migrating from Xamarin.Forms, recommend CollectionView (see Migrating from ListView).
  • The problem is really a binding, DI, or navigation problem that happens to involve a list — defer to maui-data-binding, maui-dependency-injection, or maui-shell-navigation.

The API sections below are a reference, not a checklist — offer them only when relevant. Four rules are non-negotiable, because violating them produces code that does not work or silently loses compile-time checking:

  1. Never use ViewCell as a DataTemplate root in CollectionView.
  2. Use ObservableCollection<T> when the list mutates after first render.
  3. Mutate the bound collection on the UI thread.
  4. Set x:DataType on every DataTemplate (and on the page root) for compiled bindings.

Everything else — sizing strategy, snap points, header/footer, empty views — is optional and should be offered only when it addresses the user's actual problem.

Inputs

  • A data source (typically ObservableCollection<T>) bound to ItemsSource
  • A DataTemplate defining how each item renders
  • Optional: layout configuration, selection mode, grouping model, empty view

Basic Setup

A complete, copy-pasteable page. Two things are load-bearing: the xmlns:models declaration that every x:DataType="models:Item" in this skill assumes, and the root x:DataType — without it the outer ItemsSource binding is not compiled:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:MyApp.Models"
             xmlns:vm="clr-namespace:MyApp.ViewModels"
             x:DataType="vm:ItemsViewModel"
             x:Class="MyApp.ItemsPage">
    <ContentPage.BindingContext>
        <vm:ItemsViewModel />
    </ContentPage.BindingContext>
    <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>
</ContentPage>

Later snippets show only the CollectionView element. When you hand a snippet to a user, include the matching xmlns: declaration for any prefix it uses, or the XAML will not compile.

The inline <ContentPage.BindingContext> above keeps the example self-contained. In an app that uses dependency injection, register the ViewModel instead and assign it through constructor injection (BindingContext = vm;) — see the maui-dependency-injection skill.

Key rules:

  • Bind ItemsSource to an ObservableCollection<T> so the UI updates on add/remove.
  • Each item template root must be a Layout or Viewnever use ViewCell.
  • Always set x:DataType on DataTemplate for compiled bindings.

Layouts

Set ItemsLayout to control arrangement. Default is VerticalList.

LayoutXAML value
Vertical listVerticalList (default)
Horizontal listHorizontalList
Vertical gridGridItemsLayout with Orientation="Vertical"
Horizontal gridGridItemsLayout with Orientation="Horizontal"

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

ModeProperty to bindBinding mode
None
SingleSelectedItemTwoWay
MultipleSelectedItemsOneWay
<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;
    }
}
  1. 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.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. LinearItemsLayout and GridItemsLayout support virtualization. Using BindableLayout on a StackLayout as an alternative to CollectionView has no virtualization, which triggers infinite threshold-reached events.

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);
ScrollToPositionBehavior
MakeVisibleScrolls just enough to make the item visible
StartScrolls item to the start of the viewport
CenterScrolls item to the center of the viewport
EndScrolls item to the end of the viewport

Snap Points

<CollectionView.ItemsLayout>
    <LinearItemsLayout Orientation="Horizontal"
                       SnapPointsType="MandatorySingle"
                       SnapPointsAlignment="Center" />
</CollectionView.ItemsLayout>
  • SnapPointsType: None, Mandatory, MandatorySingle
  • SnapPointsAlignment: Start, Center, End

Migrating from ListView

ListView still compiles, but as of .NET 10 it is marked [Obsolete] ("ListView is deprecated. Please use CollectionView instead."). It is not obsolete on .NET 9 and earlier, so check the project's target framework before describing it as deprecated. If the user asks which control to use, or is migrating from Xamarin.Forms, recommend CollectionView — it is faster, needs no ViewCell, and supports flexible layouts. What to avoid is silently rewriting ListView code the user did not ask you to touch.

ListViewCollectionView equivalent
ViewCell template rootAny View/Layout root — ViewCell is not supported
ItemSelected eventSelectionChanged event, or SelectionChangedCommand
ItemTapped eventA TapGestureRecognizer in the item template — SelectionChanged only fires when the selection changes, so it will not re-fire on tapping the already-selected item
IsPullToRefreshEnabled + RefreshingWrap the CollectionView in a RefreshView
IsGroupingEnabledIsGrouped
HasUnevenRows="True"Default ItemSizingStrategy="MeasureAllItems"
RowHeight (fixed height)Set the height in the item template. MeasureFirstItem only reuses the first item's measured size — it is not an explicit row height
SeparatorVisibility / SeparatorColorNo equivalent — draw a BoxView/Border in the item template

The missing separator API is the most common migration surprise: CollectionView has no built-in separators, so add one to the template yourself.

Performance Tips

Apply these only when the user reports a performance problem or explicitly asks about performance — they are not a default checklist.

  • Use MeasureFirstItem for uniform item sizes — significantly faster than the default MeasureAllItems, which measures every item individually. Set it on the CollectionView itself (it is declared on StructuredItemsView), not on LinearItemsLayout / GridItemsLayout:
    <CollectionView ItemsSource="{Binding Items}"
                    ItemSizingStrategy="MeasureFirstItem">
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="models:Item">
                <Grid Padding="8" ColumnDefinitions="44,*" ColumnSpacing="8">
                    <Image WidthRequest="44" HeightRequest="44" />
                    <Label Grid.Column="1" Text="{Binding Name}" VerticalOptions="Center" />
                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
    
    When MeasureFirstItem is the wrong choice — keep the default MeasureAllItems if:
    • Items vary in height (wrapping text, optional rows, images of differing aspect) — the first item's size is applied to all, so the rest are clipped or stretched.
    • A DataTemplateSelector returns different templates — the first item won't represent the others.
    • The first item is atypical (a header-like or "featured" row) — every item inherits its size. Fixing this by reordering data is a smell; use MeasureAllItems instead.
    • Item size depends on runtime data that isn't loaded yet when the first item is measured.
  • Use ObservableCollection<T> when the list mutates after first render. It implements INotifyCollectionChanged, so in-place Add/Remove/Insert update the UI incrementally. A List<T> is fine for a list that never changes after it is bound. Note that replacing ItemsSource re-renders everything regardless of the collection type — so mutate the bound collection in place rather than reassigning it.
  • Update collections on the UI threadMainThread.BeginInvokeOnMainThread(() => Items.Add(item)).

Common Pitfalls

IssueFix
UI doesn't update when items changeUse ObservableCollection<T>, not List<T>.
App crashes or blank itemsNever use ViewCell — use Grid, StackLayout, or any View as template root.
Items disappear or layout breaksAlways update ItemsSource and the collection on the UI thread (MainThread.BeginInvokeOnMainThread).
Incremental loading fires endlesslyDon't use StackLayout as layout; use LinearItemsLayout or GridItemsLayout.
EmptyView doesn't render correctlyWrap custom empty views in ContentView.
Poor scroll performanceUse MeasureFirstItem sizing strategy for uniform item sizes.
ItemSizingStrategy doesn't compileIt is declared on StructuredItemsView — set it on <CollectionView>, not on <LinearItemsLayout> / <GridItemsLayout>.
Items clipped or stretchedMeasureFirstItem assumes uniform item size. Use the default MeasureAllItems for variable-height items.
Selected state not visibleAdd VisualState Name="Selected" to the item template root element.
Binding errors in SwipeView commandsUse RelativeSource AncestorType to reach the ViewModel from inside the item template.

Validation

Before returning CollectionView markup you wrote or edited, confirm:

  • The DataTemplate root is a View/Layoutnot ViewCell.
  • DataTemplate declares x:DataType for compiled bindings.
  • ItemsSource is bound to ObservableCollection<T> if the list mutates.
  • ItemSizingStrategy (if used) is on <CollectionView>, not on the layout.
  • Multiple selection binds SelectedItems; Single binds SelectedItem (TwoWay).
  • RefreshView.IsRefreshing is set back to false when the refresh completes.
  • The answer covers only what the user asked — no unrequested sections.

References

Frequently asked questions

What to verify before installation and use

What does the maui-collectionview source document cover?

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.

How do I install maui-collectionview?

The source record exposes this install command: npx skills add https://github.com/dotnet/skills --skill "plugins/dotnet-maui/skills/maui-collectionview". Inspect the command and pinned source before running it.

Which permission-related actions were detected?

Static rules flagged network in the source; the page lists the matching lines and excerpts.

Alternatives

Compare before choosing

Computed 10045,643

coreyhaines31/marketingskills

ab-testing

When the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program

Computed 10045,643

coreyhaines31/marketingskills

churn-prevention

When the user wants to reduce churn, build cancellation flows, set up save offers, recover failed payments, or implement retention strategies. Also use when the user mentions 'churn,' 'cancel flow,' 'offboarding,' 'save offer,' 'dunning,' 'failed payment recovery,' 'win-back,' 'retention,' 'exit survey,' 'pause subscription,' 'involuntary churn,' 'people keep canceling,' 'churn rate is too high,' 'how do I keep users,' or 'customers are leaving.' Use this whenever someone is losing subscribers o

Computed 10014,678

prowler-cloud/prowler

postgresql-indexing

PostgreSQL indexing best practices for Prowler: index design, partial indexes, partitioned table indexing, EXPLAIN ANALYZE validation, concurrent operations, monitoring, and maintenance. Trigger: When creating or modifying PostgreSQL indexes, analyzing query performance with EXPLAIN, debugging slow queries, reviewing index usage statistics, reindexing, dropping indexes, or working with partitioned table indexes. Also trigger when discussing index strategies, partial indexes, or index maintenance

Computed 100146

oaustegard/claude-skills

featuring

Generate hierarchical _FEATURES.md files that describe what a codebase DOES from a user/consumer perspective, anchored to source symbols via tree-sitting. Supports large complex codebases through feature-driven decomposition into sub-feature files. Uses a multi-pass synthesis: orientation → detail → overview rewrite. Use when someone says "what does this do", "document features", "feature inventory", "_FEATURES.md", or needs to understand a codebase's purpose before modifying it. Complements tre