If your .NET MAUI app has a list — and most do — .NET 10 has something to say about it. ListView, the original list control that every Xamarin.Forms developer knows, is officially deprecated. CollectionView is now the unambiguous default, and the new project templates reflect this.
This isn’t just a recommendation change. Deprecation in .NET means you’ll see build warnings today and face removal in a future version. Here’s everything you need to know.
Table of Contents
- Why Did This Change Happen in .NET 10?
- What “Deprecated” Actually Means
- CollectionView as the New Default
- .NET 10 CollectionView New Features
- Migration Guide: ListView to CollectionView
- Migrating Common ListView Patterns
- Other .NET 10 MAUI Changes
- Upgrade Checklist
- FAQ
- Conclusion
Why Did This Change Happen in .NET 10?
ListView was designed for Xamarin.Forms and carries significant architectural baggage. It was built on platform-specific list implementations with minimal abstraction — on Android it used a ListView widget, on iOS a UITableView. Every customization required platform-specific renderer overrides.
ListView’s Known Limitations
- Requires
ViewCellwrappers around all item templates — adds overhead and XML noise - No built-in grid layout — only vertical lists
- No native multiple selection API
- No EmptyView support without hacks
- Group headers are clunky compared to CollectionView’s implementation
- Performance degrades measurably on large lists due to non-virtualized rendering in some scenarios
CollectionView was purpose-built to fix every one of these issues. With two major MAUI versions of CollectionView proving its stability, the MAUI team was ready to make the call.
What “Deprecated” Actually Means
In .NET, deprecation follows a defined lifecycle:
- Deprecated (.NET 10) —
[Obsolete]attribute applied, IDE warnings shown, build warnings appear with warning codes - Removed (future version) — compile errors if still used; typically 2 major versions after deprecation
Current status: ListView is decorated with [Obsolete(DiagnosticId = "MA0001")] in .NET 10. You can suppress warnings per-project if needed:
<!-- .csproj — suppress ListView deprecation warnings temporarily -->
<PropertyGroup>
<NoWarn>$(NoWarn);MA0001</NoWarn>
</PropertyGroup>
This buys time, but the deprecation warning exists for a reason. Plan your migration within the next 1–2 release cycles.
CollectionView as the New Default
Starting with .NET 10, all MAUI project templates and scaffolding use CollectionView exclusively. The official documentation landing pages for “displaying lists” now point to CollectionView with no mention of ListView.
New Template Output
<!-- .NET 10 dotnet new maui template output -->
<CollectionView ItemsSource="{Binding Items}"
SelectionMode="Single"
SelectedItem="{Binding SelectedItem}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:ItemModel">
<Grid Padding="10">
<Label Text="{Binding Title}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
.NET 10 CollectionView New Features
Beyond being the default, CollectionView itself gains new capabilities in .NET 10.
Scroll Anchoring Modes
Three new scroll anchoring modes control behavior when items are added:
<CollectionView ItemsUpdatingScrollMode="KeepLastItemInView">
<!-- Also available: KeepScrollOffset, KeepItemsInView -->
KeepLastItemInView— scrolls to show the last item when new items are added (ideal for chat)KeepScrollOffset— maintains current scroll offset when items prepend (ideal for “load earlier” pattern)KeepItemsInView— keeps currently visible items in view (default)
Header and Footer Templates
Sticky headers and footers now work correctly on all platforms in .NET 10 — previously they had inconsistent behavior on Android:
<CollectionView ItemsSource="{Binding Products}">
<CollectionView.Header>
<Label Text="Product Catalog"
FontSize="20"
FontAttributes="Bold"
Padding="12,16" />
</CollectionView.Header>
<CollectionView.Footer>
<Button Text="Load More"
Command="{Binding LoadMoreCommand}"
Margin="12" />
</CollectionView.Footer>
</CollectionView>
Improved Grouping Performance
Group header rendering performance improved significantly on Android — group headers now render using the same virtualization path as item cells, rather than being rendered outside the recycling pool.
<CollectionView ItemsSource="{Binding GroupedProducts}"
IsGrouped="True">
<CollectionView.GroupHeaderTemplate>
<DataTemplate x:DataType="model:ProductGroup">
<Label Text="{Binding CategoryName}"
BackgroundColor="#F5F5F5"
Padding="12,8"
FontAttributes="Bold" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
</CollectionView>
Migration Guide: ListView to CollectionView
The migration is mostly mechanical. Here’s the full mapping:
<!-- BEFORE: ListView -->
<ListView x:Name="MyList"
ItemsSource="{Binding Items}"
HasUnevenRows="True"
SeparatorVisibility="None"
SeparatorColor="Transparent"
IsPullToRefreshEnabled="True"
RefreshCommand="{Binding RefreshCommand}"
IsRefreshing="{Binding IsRefreshing}"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="12">
<Label Text="{Binding Title}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- AFTER: CollectionView -->
<RefreshView Command="{Binding RefreshCommand}"
IsRefreshing="{Binding IsRefreshing}">
<CollectionView x:Name="MyList"
ItemsSource="{Binding Items}"
SelectionMode="Single"
SelectionChanged="OnSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="12">
<Label Text="{Binding Title}" />
</Grid>
<!-- No ViewCell needed -->
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
Key differences to remember:
- Remove all
ViewCellwrappers from DataTemplates - Pull-to-refresh moves to a
RefreshViewwrapper (CollectionView doesn’t have it built in) ItemSelectedevent →SelectionChangedevent (different event args)HasUnevenRowsis not needed — CollectionView measures each item automaticallySeparatorVisibilityis not available — add separators manually in the DataTemplate if needed
Migrating Common ListView Patterns
Cell Tap Navigation
// ListView (old)
private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem is Product product)
Navigation.PushAsync(new ProductDetailPage(product));
((ListView)sender).SelectedItem = null; // Deselect
}
// CollectionView (new) — use command binding in ViewModel
[RelayCommand]
private async Task SelectProductAsync(Product product)
{
await Shell.Current.GoToAsync("productdetail",
new Dictionary<string, object> { { "Product", product } });
}
// XAML
<CollectionView SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Product">
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:ProductListViewModel}}, Path=SelectProductCommand}"
CommandParameter="{Binding .}" />
</Grid.GestureRecognizers>
<Label Text="{Binding Name}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Context Actions (Swipe to Delete)
<!-- ListView context actions → CollectionView SwipeView -->
<CollectionView ItemsSource="{Binding Items}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:TaskItem">
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Delete"
BackgroundColor="Red"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:TaskViewModel}}, Path=DeleteCommand}"
CommandParameter="{Binding .}" />
</SwipeItems>
</SwipeView.RightItems>
<Grid Padding="12">
<Label Text="{Binding Title}" />
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Other .NET 10 MAUI Changes Worth Knowing
- Frame → Border:
Frameis deprecated alongside ListView. Replace withBorderandStrokeShape="RoundRectangle 12" - Minimum platform targets raised: iOS 15+ and Android API 24+ required
- Device.RuntimePlatform removed: use
DeviceInfo.Platform(was deprecated since .NET 8) - .NET Aspire integration: MAUI apps can now join an Aspire AppHost for local dev service discovery
- New Shell route guards: intercept navigation for auth checks without hacking Shell’s navigation pipeline
Upgrade Checklist
- ☐ Update target frameworks to
net10.0-android,net10.0-ios, etc. in.csproj - ☐ Run build — collect all
MA0001(ListView) andMA0002(Frame) deprecation warnings - ☐ Replace each
ListViewwithCollectionView(remove ViewCell wrappers) - ☐ Move pull-to-refresh from ListView to
RefreshViewwrappers - ☐ Migrate
ItemSelectedevent handlers toSelectionChangedor command bindings - ☐ Replace
FramewithBorder - ☐ Remove any remaining
Device.RuntimePlatformcalls →DeviceInfo.Platform - ☐ Verify iOS minimum deployment target is 15.0+
- ☐ Test all list screens on Android emulator and iOS simulator
FAQ
When will ListView actually be removed from MAUI?
No specific removal version is announced, but the pattern for .NET deprecations is removal 2 major versions after deprecation. That puts likely removal at .NET 12. Don’t wait — migrate in your next sprint cycle.
Does CollectionView support the exact same cell types as ListView?
CollectionView DataTemplates accept any View — remove the ViewCell wrapper and put your layout directly in the DataTemplate. TextCell, ImageCell, and other built-in cell types don’t have CollectionView equivalents — recreate their UI with Grid/Label/Image directly.
My ListView uses grouping with alphabet scrubber on iOS — does CollectionView support this?
The native iOS alphabet scrubber (section index) is not built into CollectionView. It requires a custom handler implementation. If this is a hard requirement, keep the ListView temporarily while building the handler, then migrate.
Is there a performance difference between ListView and CollectionView for small lists?
For under 20 items, the difference is negligible and not user-perceptible. Migrate for code consistency and to avoid future forced migration, not for performance gains at small scale.
Can I use both ListView and CollectionView in the same app during a phased migration?
Yes — suppress the MA0001 warning project-wide and migrate screen by screen. There’s no technical conflict with using both controls simultaneously.
Conclusion
The ListView deprecation in .NET 10 is the right call — CollectionView is strictly better in every measurable way. The migration is straightforward for most screens: remove ViewCell, move pull-to-refresh to RefreshView, update event handlers. The more complex cases (grouping with native scrubbers, heavy custom cells) need more planning but are still manageable.
Start with your highest-traffic screens and work through the build warning list systematically. Getting ahead of this now prevents a forced all-at-once migration when removal happens in a future release.
[INTERNAL_LINK: Migrating from ListView to CollectionView complete guide] [INTERNAL_LINK: .NET MAUI in .NET 10 all new features explained]

Leave a Reply