| name | maui-data-binding |
| description | Guidance for .NET MAUI XAML data bindings, compiled bindings, value converters, binding modes, multi-binding, relative bindings, and MVVM best practices. USE FOR: "data binding", "compiled binding", "value converter", "IValueConverter", "binding mode", "TwoWay binding", "multi-binding", "relative binding", "BindingContext", "MVVM binding", "INotifyPropertyChanged". DO NOT USE FOR: CollectionView item templates (use maui-collectionview), Shell navigation data passing (use maui-shell-navigation), or dependency injection (use maui-dependency-injection). |
.NET MAUI Data Binding
Don't specify redundant binding modes
Set Mode explicitly only when overriding the default. Most properties already have the right default:
<Label Text="{Binding Score}" />
<Entry Text="{Binding UserName}" />
<Switch IsToggled="{Binding DarkMode}" />
<Label Text="{Binding Title, Mode=OneTime}" />
<Entry Text="{Binding SearchQuery, Mode=OneWayToSource}" />
<Label Text="{Binding Score, Mode=OneWay}" />
<Entry Text="{Binding UserName, Mode=TwoWay}" />
Compiled bindings — x:DataType placement rules
Compiled bindings are 8–20× faster than reflection-based bindings. Enable with x:DataType.
Place x:DataType only where BindingContext is set:
- Page root — where you assign
BindingContext.
- DataTemplate — which creates a new binding scope.
Do not scatter x:DataType on child elements. Adding x:DataType="x:Object" on children to "escape" compiled bindings is an anti-pattern — it disables compile-time checking and reintroduces reflection. To opt out for a single binding, use x:DataType="{x:Null}" on that element (see also: maui-xaml-authoring skill).
<ContentPage x:DataType="vm:MainViewModel">
<VerticalStackLayout>
<Label Text="{Binding Title}" />
<Slider Value="{Binding Progress}" />
</VerticalStackLayout>
</ContentPage>
<ContentPage x:DataType="vm:MainViewModel">
<VerticalStackLayout>
<Label Text="{Binding Title}" />
<Slider x:DataType="x:Object" Value="{Binding Progress}" />
</VerticalStackLayout>
</ContentPage>
DataTemplate always needs its own x:DataType:
<CollectionView ItemsSource="{Binding People}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Person">
<Label Text="{Binding FullName}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Treat compiler warnings as errors in CI
| Warning | Meaning |
|---|
| XC0022 | Binding path not found on the declared x:DataType |
| XC0023 | Property is not bindable |
| XC0024 | x:DataType type not found |
| XC0025 | Binding used without x:DataType (non-compiled fallback) |
<WarningsAsErrors>XC0022;XC0025</WarningsAsErrors>
.NET 9+ compiled code bindings
Fully AOT-safe, no reflection:
label.SetBinding(Label.TextProperty,
static (PersonViewModel vm) => vm.FullName);
entry.SetBinding(Entry.TextProperty,
static (PersonViewModel vm) => vm.Age,
mode: BindingMode.TwoWay,
converter: new IntToStringConverter());
Threading caveat
MAUI automatically marshals PropertyChanged to the UI thread — you can raise it from any thread. However, direct ObservableCollection mutations (Add/Remove) from background threads may still crash:
await Task.Run(() => Items = LoadData());
MainThread.BeginInvokeOnMainThread(() => Items.Add(newItem));
Performance tips
- Compiled bindings eliminate reflection — always prefer them.
- NativeAOT / trimming: Reflection-based bindings break under trimming. Compiled bindings (XAML
x:DataType or code SetBinding with lambdas) are trimmer- and AOT-safe.
- Use
OneTime mode for truly static data to skip change-tracking registration.
- Avoid complex converter chains in hot paths — pre-compute values in the ViewModel instead.