Skip to main content

maui-development

.NET MAUI UI development: XAML patterns, MVVM with CommunityToolkit.Mvvm, layout examples, and performance optimization. Use when creating or modifying MAUI UI, writing XAML, implementing ViewModels, or optimizing performance.

Source facts

Repository
visionarycoder/vc.Agentic
Last source activity
November 9, 2026 at 06:22
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
maui-development
description
.NET MAUI UI development: XAML patterns, MVVM with CommunityToolkit.Mvvm, layout examples, and performance optimization. Use when creating or modifying MAUI UI, writing XAML, implementing ViewModels, or optimizing performance.
metadata
{"metadata-version":"1.0.0","title":".NET MAUI Development","document-type":"skill","repository":"vc.Agentic","language":"Standard Technical English","updated":"2026-11-08","collection":"dotnet"}
# .NET MAUI Development ## Layout Controls ### Grid ```xml <Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="*,2*"> <Label Grid.Row="0" Text="Header" /> <CollectionView Grid.Row="1" Grid.ColumnSpan="2" /> <Button Grid.Row="2" Grid.Column="1" Text="Submit" /> </Grid> ``` ### Border (Preferred over Frame) ```xml <Border Stroke="{StaticResource Gray300}" StrokeThickness="1" StrokeShape="RoundRectangle 8" Padding="16"> <Label Text="Content" /> </Border> ``` ### ScrollView (Must be in Grid) ```xml <Grid> <ScrollView> <VerticalStackLayout Padding="16"> <!-- Content --> </VerticalStackLayout> </ScrollView> </Grid> ``` ## Data Display ### CollectionView ```xml <CollectionView ItemsSource="{Binding Items}"> <CollectionView.ItemTemplate> <DataTemplate x:DataType="models:Item"> <Border Padding="12"> <Label Text="{Binding Name}" /> </Border> </DataTemplate> </CollectionView.ItemTemplate> <CollectionView.EmptyView> <Label Text="No items" HorizontalOptions="Center" /> </CollectionView.EmptyView> </CollectionView> ``` ### BindableLayout (≤20 items) ```xml <VerticalStackLayout BindableLayout.ItemsSource="{Binding SmallList}"> <BindableLayout.ItemTemplate> <DataTemplate x:DataType="models:Item"> <Label Text="{Binding Name}" /> </DataTemplate> </BindableLayout.ItemTemplate> </VerticalStackLayout> ``` ## Input Controls ```xml <Entry Placeholder="Email" Text="{Binding Email}" Keyboard="Email" /> <Editor Text="{Binding Description}" AutoSize="TextChanges" /> <Picker ItemsSource="{Binding Options}" SelectedItem="{Binding Selected}" /> <Switch IsToggled="{Binding IsEnabled}" /> <Slider Minimum="0" Maximum="100" Value="{Binding Progress}" /> <DatePicker Date="{Binding SelectedDate}" /> <SearchBar Text="{Binding SearchText}" SearchCommand="{Binding SearchCommand}" /> ``` ## Interactive Controls ### RefreshView ```xml <RefreshView IsRefreshing="{Binding IsRefreshing}" Command="{Binding RefreshCommand}"> <CollectionView ItemsSource="{Binding Items}" /> </RefreshView> ``` ### SwipeView ```xml <SwipeView> <SwipeView.RightItems> <SwipeItems> <SwipeItem Text="Delete" Background="Red" Command="{Binding DeleteCommand}" /> </SwipeItems> </SwipeView.RightItems> <Label Text="{Binding Name}" /> </SwipeView> ``` ## Images (Always .png) ```xml <Image Source="icon.png" Aspect="AspectFit" /> ``` --- # XAML Patterns ## Page Template ```xml <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:vm="clr-namespace:MyApp.ViewModels" x:Class="MyApp.Views.MyPage" x:DataType="vm:MyViewModel" Title="{Binding Title}"> <Grid RowDefinitions="Auto,*,Auto"> <Label Grid.Row="0" Text="Header" /> <ScrollView Grid.Row="1"> <VerticalStackLayout Padding="16" Spacing="8"> <!-- Content --> </VerticalStackLayout> </ScrollView> <Button Grid.Row="2" Text="Action" Command="{Binding ActionCommand}" /> </Grid> </ContentPage> ``` ## Compiled Bindings (Required) ```xml <!-- Page level --> <ContentPage x:DataType="vm:MainViewModel"> <Label Text="{Binding Title}" /> </ContentPage> <!-- DataTemplate (REQUIRED) --> <DataTemplate x:DataType="models:Item"> <Label Text="{Binding Name}" /> </DataTemplate> ``` ## Binding Modes ```xml <Label Text="{Binding Id, Mode=OneTime}" /> <!-- Static data --> <Label Text="{Binding Name}" /> <!-- Read-only (default) --> <Entry Text="{Binding Name, Mode=TwoWay}" /> <!-- Editable --> ``` ## Binding to Parent (in DataTemplates) ```xml <Button Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}, Path=DeleteCommand}" CommandParameter="{Binding}" /> ``` ## String Formatting ```xml <Label Text="{Binding Price, StringFormat='Price: {0:C}'}" /> <Label Text="{Binding Date, StringFormat='{0:MMM dd, yyyy}'}" /> ``` ## Resources & Styles ```xml <Application.Resources> <Color x:Key="Primary">#512BD4</Color> <!-- Implicit (auto-applied) --> <Style TargetType="Button"> <Setter Property="Background" Value="{StaticResource Primary}" /> </Style> <!-- Explicit (must reference) --> <Style x:Key="HeaderLabel" TargetType="Label"> <Setter Property="FontSize" Value="24" /> </Style> </Application.Resources> <Label Style="{StaticResource HeaderLabel}" /> ``` ## Data Triggers ```xml <Button Text="Submit"> <Button.Triggers> <DataTrigger TargetType="Button" Binding="{Binding IsValid}" Value="False"> <Setter Property="IsEnabled" Value="False" /> <Setter Property="Opacity" Value="0.5" /> </DataTrigger> </Button.Triggers> </Button> ``` ## Common Patterns ### Loading State ```xml <Grid> <ScrollView IsVisible="{Binding IsNotBusy}"> <!-- Content --> </ScrollView> <ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" /> </Grid> ``` ### Empty State ```xml <CollectionView.EmptyView> <VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center"> <Label Text="No items found" /> <Button Text="Add Item" Command="{Binding AddCommand}" /> </VerticalStackLayout> </CollectionView.EmptyView> ``` --- # MVVM with CommunityToolkit.Mvvm ## ViewModel Pattern ```csharp using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; public partial class MainViewModel : ObservableObject { private readonly IMyService _service; public MainViewModel(IMyService service) { _service = service; } [ObservableProperty] private string _title = "Welcome"; [ObservableProperty] [NotifyPropertyChangedFor(nameof(IsNotBusy))] private bool _isBusy; public bool IsNotBusy => !IsBusy; [RelayCommand] private async Task LoadAsync() { if (IsBusy) return; try { IsBusy = true; await _service.LoadAsync(); } finally { IsBusy = false; } } } ``` ## Observable Properties ```csharp [ObservableProperty] private string _name = string.Empty; // Dependent property [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] private string _firstName = string.Empty; public string FullName => $"{FirstName} {LastName}"; // Change callback partial void OnSearchTextChanged(string value) { FilterItems(value); } // CanExecute notification [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(SubmitCommand))] private string _email = string.Empty; ``` ## Commands ```csharp [RelayCommand] private void Increment() => Count++; [RelayCommand] private async Task RefreshAsync() => await LoadAsync(); [RelayCommand] private void Select(Item item) => Selected = item; [RelayCommand(CanExecute = nameof(CanSubmit))] private async Task SubmitAsync() { } private bool CanSubmit() => !string.IsNullOrWhiteSpace(Email); ``` ## Dependency Injection ```csharp // MauiProgram.cs builder.Services.AddSingleton<IMyService, MyService>(); builder.Services.AddTransient<MainViewModel>(); builder.Services.AddTransient<MainPage>(); // Page public partial class MainPage : ContentPage { public MainPage(MainViewModel vm) { BindingContext = vm; InitializeComponent(); } } ``` ## Shell Navigation ```csharp // Register routes Routing.RegisterRoute(nameof(DetailPage), typeof(DetailPage)); // Navigate await Shell.Current.GoToAsync($"{nameof(DetailPage)}?id={item.Id}"); await Shell.Current.GoToAsync(".."); // Back // Receive parameters [QueryProperty(nameof(ItemId), "id")] public partial class DetailViewModel : ObservableObject { [ObservableProperty] private string _itemId = string.Empty; partial void OnItemIdChanged(string value) => LoadCommand.Execute(null); } ``` ## Collections ```csharp [ObservableProperty] private ObservableCollection<Item> _items = new(); [RelayCommand] private async Task LoadItemsAsync() { var data = await _service.GetItemsAsync(); Items.Clear(); foreach (var item in data) Items.Add(item); } ``` ## Messaging ```csharp // Message public record ItemAddedMessage(Item Item); // Send WeakReferenceMessenger.Default.Send(new ItemAddedMessage(item)); // Receive public partial class MyViewModel : ObservableObject, IRecipient<ItemAddedMessage> { public MyViewModel() => WeakReferenceMessenger.Default.Register(this); public void Receive(ItemAddedMessage msg) => Items.Add(msg.Item); } ``` --- # Performance ## Startup Performance ```csharp // Defer work to OnAppearing protected override async void OnAppearing() { base.OnAppearing(); await ViewModel.LoadCommand.ExecuteAsync(null); } ``` ## Memory Management ```csharp // Unsubscribe events protected override void OnDisappearing() { base.OnDisappearing(); MyService.DataChanged -= OnDataChanged; } ``` ## Binding Tips ```xml <!-- OneTime for static data --> <Label Text="{Binding UserId, Mode=OneTime}" /> <!-- Don't bind static values --> <Label Text="My App" /> <!-- Not {Binding AppName} --> ``` ## Performance Checklist - [ ] Events unsubscribed - [ ] AOT enabled for Release
View on GitHub