
Maui Data Binding
- 64 installs
- 163 repo stars
- Updated July 6, 2026
- davidortinau/maui-skills
Covers .NET MAUI XAML data bindings, compiled bindings, value converters, binding modes, multi-binding, relative bindings, and MVVM patterns.
About
Guides .NET MAUI XAML data binding including compiled bindings, value converters, binding modes, multi-binding, relative bindings and MVVM best practices. A developer uses it when wiring view models to XAML views in a MAUI app.
- Compiled bindings, value converters, and binding modes
- Multi-binding, relative bindings, and MVVM patterns
Maui Data Binding by the numbers
- 64 all-time installs (skills.sh)
- Ranked #577 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-data-bindingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 64 |
|---|---|
| repo stars | ★ 163 |
| Last updated | July 6, 2026 |
| Repository | davidortinau/maui-skills ↗ |
What it does
Covers .NET MAUI XAML data bindings, compiled bindings, value converters, binding modes, multi-binding, relative bindings, and MVVM patterns.
Files
.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:
<!-- ✅ Defaults — omit Mode -->
<Label Text="{Binding Score}" /> <!-- OneWay is the default -->
<Entry Text="{Binding UserName}" /> <!-- TwoWay is the default -->
<Switch IsToggled="{Binding DarkMode}" /> <!-- TwoWay is the default -->
<!-- ✅ Override when needed -->
<Label Text="{Binding Title, Mode=OneTime}" />
<Entry Text="{Binding SearchQuery, Mode=OneWayToSource}" />
<!-- ❌ Redundant — just noise -->
<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:
1. Page root — where you assign BindingContext. 2. 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).
<!-- ✅ Correct: x:DataType only where BindingContext is set -->
<ContentPage x:DataType="vm:MainViewModel">
<VerticalStackLayout>
<Label Text="{Binding Title}" />
<Slider Value="{Binding Progress}" />
</VerticalStackLayout>
</ContentPage>
<!-- ❌ Wrong: x:DataType scattered on children -->
<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:
// ✅ Safe for PropertyChanged
await Task.Run(() => Items = LoadData());
// ⚠️ ObservableCollection.Add — dispatch to UI thread
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:DataTypeor codeSetBindingwith 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.
Data Binding API Reference
Binding Modes
| Mode | Direction | Use case |
|---|---|---|
OneWay | Source → Target | Display-only (default for most properties) |
TwoWay | Source ↔ Target | Editable controls (Entry.Text, Switch.IsToggled) |
OneWayToSource | Target → Source | Read user input without pushing back to UI |
OneTime | Source → Target (once) | Static values; no change tracking overhead |
BindingContext and Property Paths
- Every
BindableObjectinheritsBindingContextfrom its parent unless explicitly set. - Property paths support dot notation and indexers:
<Label Text="{Binding Address.City}" />
<Label Text="{Binding Items[0].Name}" />- Set
BindingContextin XAML or code-behind:
<ContentPage xmlns:vm="clr-namespace:MyApp.ViewModels"
x:DataType="vm:MainViewModel">
<ContentPage.BindingContext>
<vm:MainViewModel />
</ContentPage.BindingContext>
</ContentPage>IValueConverter
Implement IValueConverter with Convert (source → target) and ConvertBack (target → source):
public class IntToBoolConverter : IValueConverter
{
public object? Convert(object? value, Type targetType,
object? parameter, CultureInfo culture)
=> value is int i && i != 0;
public object? ConvertBack(object? value, Type targetType,
object? parameter, CultureInfo culture)
=> value is true ? 1 : 0;
}Declaring converters in XAML resources
<ContentPage.Resources>
<local:IntToBoolConverter x:Key="IntToBool" />
</ContentPage.Resources>
<Switch IsToggled="{Binding Count, Converter={StaticResource IntToBool}}" />ConverterParameter
ConverterParameter is always passed as a string. Parse it inside Convert:
<Label Text="{Binding Score, Converter={StaticResource ThresholdConverter},
ConverterParameter=50}" />int threshold = int.Parse((string)parameter);StringFormat
Use Binding.StringFormat for simple display formatting without a converter:
<Label Text="{Binding Price, StringFormat='Total: {0:C2}'}" />
<Label Text="{Binding DueDate, StringFormat='{0:MMM dd, yyyy}'}" />Note: Wrap the format string in single quotes when it contains commas or braces.
Multi-Binding
Combine multiple source values with IMultiValueConverter:
<Label>
<Label.Text>
<MultiBinding Converter="{StaticResource FullNameConverter}"
StringFormat="{}{0}">
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
</Label.Text>
</Label>public class FullNameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
if (values.Length == 2 && values[0] is string first && values[1] is string last)
return $"{first} {last}";
return string.Empty;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}Relative Bindings
| Source | Syntax | Use case |
|---|---|---|
| Self | {Binding Source={RelativeSource Self}, Path=Width} | Bind to own properties |
| Ancestor | {Binding Source={RelativeSource AncestorType={x:Type vm:ParentVM}}, Path=Title} | Reach parent BindingContext |
| TemplatedParent | {Binding Source={RelativeSource TemplatedParent}, Path=Padding} | Inside ControlTemplate |
<!-- Square box: Height = Width -->
<BoxView WidthRequest="100"
HeightRequest="{Binding Source={RelativeSource Self}, Path=WidthRequest}" />Binding Fallbacks
- FallbackValue – used when the binding path cannot be resolved or the converter throws.
- TargetNullValue – used when the bound value is
null.
<Label Text="{Binding MiddleName, TargetNullValue='(none)',
FallbackValue='unavailable'}" />
<Image Source="{Binding AvatarUrl, TargetNullValue='default_avatar.png'}" />